Merge m-c to b2g-inbound on a CLOSED TREE. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-06-12 17:33:34 -04:00
commit 774c924a5d
85 changed files with 1161 additions and 196 deletions

View File

@ -3,7 +3,7 @@
- You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
@ -42,7 +42,9 @@ function init_all() {
this.removeAttribute("keyboard-navigation");
});
gotoPref("paneGeneral");
if (document.getElementById("category-general").selected) {
gotoPref("paneGeneral");
}
}
function selectCategory(name) {

View File

@ -6,6 +6,7 @@ support-files =
[browser_advanced_update.js]
[browser_bug410900.js]
[browser_bug731866.js]
[browser_bug1020245_openPreferences_to_paneContent.js]
[browser_bug795764_cachedisabled.js]
[browser_connection.js]
[browser_connection_bug388287.js]

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function test() {
waitForExplicitFinish();
let deferred = Promise.defer();
gBrowser.selectedTab = gBrowser.addTab("about:blank");
openPreferences("paneContent");
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
newTabBrowser.addEventListener("Initialized", function PrefInit() {
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
let sel = gBrowser.contentWindow.history.state;
is(sel, "paneContent", "Content pane was selected");
deferred.resolve();
gBrowser.removeCurrentTab();
});
}, true);
yield deferred.promise;
finish();
});

View File

@ -223,6 +223,14 @@ let NetMonitorController = {
this.webConsoleClient = null;
},
/**
* Checks whether the netmonitor connection is active.
* @return boolean
*/
isConnected: function() {
return !!this.client;
},
/**
* Sets up a monitoring session.
*

View File

@ -1980,6 +1980,8 @@ NetworkDetailsView.prototype = {
*/
destroy: function() {
dumpn("Destroying the NetworkDetailsView");
$("tabpanels", this.widget).removeEventListener("select", this._onTabSelect);
},
/**
@ -2068,7 +2070,10 @@ NetworkDetailsView.prototype = {
}
populated[tab] = true;
window.emit(EVENTS.TAB_UPDATED);
NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
if (NetMonitorController.isConnected()) {
NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
}
});
},

View File

@ -39,8 +39,9 @@ function test() {
'-H "Connection: keep-alive"'
].join(" ");
const EXPECTED_RESULT = Services.appinfo.OS == "WINNT" ?
EXPECTED_WIN_RESULT : EXPECTED_POSIX_RESULT;
const EXPECTED_RESULT = Services.appinfo.OS == "WINNT"
? EXPECTED_WIN_RESULT
: EXPECTED_POSIX_RESULT;
let { NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;

View File

@ -31,7 +31,7 @@ function test() {
waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() =>
NetMonitorView.editor("#response-content-textarea")
).then((aEditor) => {
is(aEditor.getText().indexOf("\u044F"), 302, // я
is(aEditor.getText().indexOf("\u044F"), 486, // я
"The text shown in the source editor is incorrect.");
is(aEditor.getMode(), Editor.modes.html,
"The mode active in the source editor is incorrect.");

View File

@ -6,6 +6,11 @@
*/
function test() {
// These test suite functions are removed from the global scope inside a
// cleanup function. However, we still need them.
let gInfo = info;
let gOk = ok;
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
@ -36,20 +41,20 @@ function test() {
}
function checkIfDestroyed(aTag) {
info("Checking if destruction is ok.");
gInfo("Checking if destruction is ok.");
ok(aMonitor._view,
gOk(aMonitor._view,
"The network monitor view object still exists (" + aTag + ").");
ok(aMonitor._controller,
gOk(aMonitor._controller,
"The network monitor controller object still exists (" + aTag + ").");
ok(aMonitor._controller._shutdown,
gOk(aMonitor._controller._shutdown,
"The network monitor controller object still exists and is destroyed (" + aTag + ").");
ok(!aMonitor._controller.client,
gOk(!aMonitor._controller.client,
"There shouldn't be a client available after destruction (" + aTag + ").");
ok(!aMonitor._controller.tabClient,
gOk(!aMonitor._controller.tabClient,
"There shouldn't be a tabClient available after destruction (" + aTag + ").");
ok(!aMonitor._controller.webConsoleClient,
gOk(!aMonitor._controller.webConsoleClient,
"There shouldn't be a webConsoleClient available after destruction (" + aTag + ").");
}
@ -75,12 +80,12 @@ function test() {
aMonitor._controller.shutdownNetMonitor()
.then(() => {
info("Shutting down again shouldn't do anything special.");
gInfo("Shutting down again shouldn't do anything special.");
checkIfDestroyed(2);
return aMonitor._controller.disconnect();
})
.then(() => {
info("Disconnecting again shouldn't do anything special.");
gInfo("Disconnecting again shouldn't do anything special.");
checkIfDestroyed(3);
});
});

View File

@ -86,10 +86,8 @@ function test() {
ok(requestItem.attachment.requestHeaders,
"There should be a requestHeaders attachment available.");
ok(requestItem.attachment.requestHeaders.headers.length >= 6,
is(requestItem.attachment.requestHeaders.headers.length, 8,
"The requestHeaders attachment has an incorrect |headers| property.");
// Can't test for an exact total number of headers, because it seems to
// vary across pgo/non-pgo builds.
isnot(requestItem.attachment.requestHeaders.headersSize, 0,
"The requestHeaders attachment has an incorrect |headersSize| property.");
// Can't test for the exact request headers size because the value may
@ -118,9 +116,9 @@ function test() {
ok(requestItem.attachment.responseHeaders,
"There should be a responseHeaders attachment available.");
is(requestItem.attachment.responseHeaders.headers.length, 6,
is(requestItem.attachment.responseHeaders.headers.length, 9,
"The responseHeaders attachment has an incorrect |headers| property.");
is(requestItem.attachment.responseHeaders.headersSize, 173,
is(requestItem.attachment.responseHeaders.headersSize, 255,
"The responseHeaders attachment has an incorrect |headersSize| property.");
verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
@ -146,7 +144,7 @@ function test() {
"The status attachment has an incorrect value.");
is(requestItem.attachment.statusText, "Och Aye",
"The statusText attachment has an incorrect value.");
is(requestItem.attachment.headersSize, 173,
is(requestItem.attachment.headersSize, 255,
"The headersSize attachment has an incorrect value.");
verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {

View File

@ -63,10 +63,8 @@ function test() {
is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
"There should be 2 header scopes displayed in this tabpanel.");
ok(tabpanel.querySelectorAll(".variable-or-property").length >= 12,
"There should be at least 12 header values displayed in this tabpanel.");
// Can't test for an exact total number of headers, because it seems to
// vary across pgo/non-pgo builds.
is(tabpanel.querySelectorAll(".variable-or-property").length, 17,
"There should be 17 header values displayed in this tabpanel.");
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
"The empty notice should not be displayed in this tabpanel.");
@ -76,7 +74,7 @@ function test() {
is(responseScope.querySelector(".name").getAttribute("value"),
L10N.getStr("responseHeaders") + " (" +
L10N.getFormatStr("networkMenu.sizeKB", L10N.numberWithDecimals(173/1024, 3)) + ")",
L10N.getFormatStr("networkMenu.sizeKB", L10N.numberWithDecimals(255/1024, 3)) + ")",
"The response headers scope doesn't have the correct title.");
ok(requestScope.querySelector(".name").getAttribute("value").contains(
@ -87,20 +85,24 @@ function test() {
// sure it's smaller than 1 MB though, so it starts with a 0.
is(responseScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
"Connection", "The first response header name was incorrect.");
"Cache-Control", "The first response header name was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
"\"close\"", "The first response header value was incorrect.");
"\"no-cache, no-store, must-revalidate\"", "The first response header value was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
"Content-Length", "The second response header name was incorrect.");
"Connection", "The second response header name was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
"\"12\"", "The second response header value was incorrect.");
"\"close\"", "The second response header value was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .name")[2].getAttribute("value"),
"Content-Type", "The third response header name was incorrect.");
"Content-Length", "The third response header name was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .value")[2].getAttribute("value"),
"\"text/plain; charset=utf-8\"", "The third response header value was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .name")[5].getAttribute("value"),
"\"12\"", "The third response header value was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .name")[3].getAttribute("value"),
"Content-Type", "The fourth response header name was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .value")[3].getAttribute("value"),
"\"text/plain; charset=utf-8\"", "The fourth response header value was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .name")[8].getAttribute("value"),
"foo-bar", "The last response header name was incorrect.");
is(responseScope.querySelectorAll(".variables-view-variable .value")[5].getAttribute("value"),
is(responseScope.querySelectorAll(".variables-view-variable .value")[8].getAttribute("value"),
"\"baz\"", "The last response header value was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
@ -108,21 +110,17 @@ function test() {
is(requestScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
"\"example.com\"", "The first request header value was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .name")[5].getAttribute("value"),
"Connection", "The penultimate request header name was incorrect.");
"Connection", "The ante-penultimate request header name was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .value")[5].getAttribute("value"),
"\"keep-alive\"", "The penultimate request header value was incorrect.");
let lastReqHeaderName = requestScope.querySelectorAll(".variables-view-variable .name")[6];
let lastReqHeaderValue = requestScope.querySelectorAll(".variables-view-variable .value")[6];
if (lastReqHeaderName && lastReqHeaderValue) {
is(lastReqHeaderName.getAttribute("value"),
"Cache-Control", "The last request header name was incorrect.");
is(lastReqHeaderValue.getAttribute("value"),
"\"max-age=0\"", "The last request header value was incorrect.");
} else {
info("The number of request headers was 6 instead of 7. Technically, " +
"not a failure in this particular test, but needs investigation.");
}
"\"keep-alive\"", "The ante-penultimate request header value was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .name")[6].getAttribute("value"),
"Pragma", "The penultimate request header name was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .value")[6].getAttribute("value"),
"\"no-cache\"", "The penultimate request header value was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .name")[7].getAttribute("value"),
"Cache-Control", "The last request header name was incorrect.");
is(requestScope.querySelectorAll(".variables-view-variable .value")[7].getAttribute("value"),
"\"no-cache\"", "The last request header value was incorrect.");
}
function testCookiesTab() {

View File

@ -15,11 +15,38 @@ function test() {
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 2).then(() => {
let divisions = $all(".requests-menu-timings-division[division-scale=second]");
let millisecondDivs = $all(".requests-menu-timings-division[division-scale=millisecond]");
let secondDivs = $all(".requests-menu-timings-division[division-scale=second]");
let minuteDivs = $all(".requests-menu-timings-division[division-scale=minute]");
ok(divisions.length,
info("Number of millisecond divisions: " + millisecondDivs.length);
info("Number of second divisions: " + secondDivs.length);
info("Number of minute divisions: " + minuteDivs.length);
for (let div of millisecondDivs) {
info("Millisecond division: " + div.getAttribute("value"));
}
for (let div of secondDivs) {
info("Second division: " + div.getAttribute("value"));
}
for (let div of minuteDivs) {
info("Minute division: " + div.getAttribute("value"));
}
is(RequestsMenu.itemCount, 2,
"There should be only two requests made.");
let firstRequest = RequestsMenu.getItemAtIndex(0);
let lastRequest = RequestsMenu.getItemAtIndex(1);
info("First request happened at: " +
firstRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
info("Last request happened at: " +
lastRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
ok(secondDivs.length,
"There should be at least one division on the seconds time scale.");
ok(divisions[0].getAttribute("value").match(/\d+\.\d{2}\s\w+/),
ok(secondDivs[0].getAttribute("value").match(/\d+\.\d{2}\s\w+/),
"The division on the seconds time scale looks legit.");
teardown(aMonitor).then(finish);

View File

@ -97,24 +97,49 @@ function removeTab(aTab, aWindow) {
targetBrowser.removeTab(aTab);
}
function waitForNavigation(aTarget) {
let deferred = promise.defer();
aTarget.once("will-navigate", () => {
aTarget.once("navigate", () => {
deferred.resolve();
});
});
return deferred.promise;
}
function reconfigureTab(aTarget, aOptions) {
let deferred = promise.defer();
aTarget.activeTab.reconfigure(aOptions, deferred.resolve);
return deferred.promise;
};
function toggleCache(aTarget, aEnabled) {
let options = { cacheEnabled: aEnabled, performReload: true };
let navigationFinished = waitForNavigation(aTarget);
return reconfigureTab(aTarget, options).then(() => navigationFinished);
}
function initNetMonitor(aUrl, aWindow) {
info("Initializing a network monitor pane.");
return addTab(aUrl).then((aTab) => {
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
info("Net tab added successfully: " + aUrl);
let deferred = promise.defer();
let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject;
let target = TargetFactory.forTab(aTab);
let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
let target = TargetFactory.forTab(tab);
gDevTools.showToolbox(target, "netmonitor").then((aToolbox) => {
info("Netork monitor pane shown successfully.");
yield target.makeRemote();
info("Target remoted.");
let monitor = aToolbox.getCurrentPanel();
deferred.resolve([aTab, debuggee, monitor]);
});
yield toggleCache(target, false);
info("Network cache disabled");
return deferred.promise;
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
info("Netork monitor pane shown successfully.");
let monitor = toolbox.getCurrentPanel();
return [tab, debuggee, monitor];
});
}

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
<style>
input {

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>
@ -25,8 +28,7 @@
}
(function performRequests() {
get("request_0", function() {
});
get("request_0", function() {});
})();
</script>
</body>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>
@ -30,7 +33,7 @@
get("sjs_content-type-test-server.sjs?sts=304&fmt=audio");
get("sjs_content-type-test-server.sjs?sts=304&fmt=video");
get("sjs_content-type-test-server.sjs?sts=304&fmt=flash");
get("test-image.png");
get("test-image.png?v=" + Math.random());
</script>
</body>

View File

@ -5,6 +5,9 @@
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Network Monitor test page</title>
</head>

View File

@ -13,8 +13,11 @@ function handleRequest(request, response) {
let cachedCount = 0;
let cacheExpire = 60; // seconds
function maybeMakeCached() {
function setCacheHeaders() {
if (status != 304) {
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
return;
}
// Spice things up a little!
@ -31,7 +34,7 @@ function handleRequest(request, response) {
case "txt": {
response.setStatusLine(request.httpVersion, status, "DA DA DA");
response.setHeader("Content-Type", "text/plain", false);
maybeMakeCached();
setCacheHeaders();
response.write("Братан, ты вообще качаешься?");
response.finish();
break;
@ -39,7 +42,7 @@ function handleRequest(request, response) {
case "xml": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("<label value='greeting'>Hello XML!</label>");
response.finish();
break;
@ -48,7 +51,7 @@ function handleRequest(request, response) {
let content = params.filter((s) => s.contains("res="))[0].split("=")[1];
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write(content || "<p>Hello HTML!</p>");
response.finish();
break;
@ -57,7 +60,7 @@ function handleRequest(request, response) {
let str = new Array(102400 /* 100 KB in bytes */).join(".");
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("<p>" + str + "</p>");
response.finish();
break;
@ -65,7 +68,7 @@ function handleRequest(request, response) {
case "css": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/css; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("body:pre { content: 'Hello CSS!' }");
response.finish();
break;
@ -73,7 +76,7 @@ function handleRequest(request, response) {
case "js": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("function() { return 'Hello JS!'; }");
response.finish();
break;
@ -81,7 +84,7 @@ function handleRequest(request, response) {
case "json": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "application/json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("{ \"greeting\": \"Hello JSON!\" }");
response.finish();
break;
@ -90,7 +93,7 @@ function handleRequest(request, response) {
let fun = params.filter((s) => s.contains("jsonp="))[0].split("=")[1];
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write(fun + "({ \"greeting\": \"Hello JSONP!\" })");
response.finish();
break;
@ -99,7 +102,7 @@ function handleRequest(request, response) {
let fun = params.filter((s) => s.contains("jsonp="))[0].split("=")[1];
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write(" " + fun + " ( { \"greeting\": \"Hello weird JSONP!\" } ) ; ");
response.finish();
break;
@ -108,7 +111,7 @@ function handleRequest(request, response) {
let str = "{ \"greeting\": \"Hello long string JSON!\" },";
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("[" + new Array(2048).join(str).slice(0, -1) + "]");
response.finish();
break;
@ -116,7 +119,7 @@ function handleRequest(request, response) {
case "json-malformed": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("{ \"greeting\": \"Hello malformed JSON!\" },");
response.finish();
break;
@ -124,7 +127,7 @@ function handleRequest(request, response) {
case "json-text-mime": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("{ \"greeting\": \"Hello third-party JSON!\" }");
response.finish();
break;
@ -132,7 +135,7 @@ function handleRequest(request, response) {
case "json-custom-mime": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
maybeMakeCached();
setCacheHeaders();
response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
response.finish();
break;
@ -140,41 +143,42 @@ function handleRequest(request, response) {
case "font": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "font/woff", false);
maybeMakeCached();
setCacheHeaders();
response.finish();
break;
}
case "image": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "image/png", false);
maybeMakeCached();
setCacheHeaders();
response.finish();
break;
}
case "audio": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "audio/ogg", false);
maybeMakeCached();
setCacheHeaders();
response.finish();
break;
}
case "video": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "video/webm", false);
maybeMakeCached();
setCacheHeaders();
response.finish();
break;
}
case "flash": {
response.setStatusLine(request.httpVersion, status, "OK");
response.setHeader("Content-Type", "application/x-shockwave-flash", false);
maybeMakeCached();
setCacheHeaders();
response.finish();
break;
}
default: {
response.setStatusLine(request.httpVersion, 404, "Not Found");
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
setCacheHeaders();
response.write("<blink>Not Found</blink>");
response.finish();
break;

View File

@ -3,6 +3,11 @@
function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 200, "Och Aye");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
response.setHeader("Foo-Bar", "baz", false);
response.write("Hello world!");

View File

@ -11,6 +11,11 @@ function handleRequest(request, response) {
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
response.setStatusLine(request.httpVersion, index == 1 ? 101 : index * 100, "Meh");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Content-Type", "text/" + index, false);
response.write(new Array(index * 10).join(index)); // + 0.01 KB
response.finish();

View File

@ -27,6 +27,11 @@ function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 501, "Not Implemented");
break;
}
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
response.write("Hello status code " + status + "!");
response.finish();

View File

@ -37,7 +37,7 @@ function testParseCssProperty() {
target.appendChild(frag);
is(target.innerHTML,
'1px solid <span style="background-color:red" class="test-colorswatch"></span><span>#F00</span>',
'1px solid <span data-color="#F00"><span style="background-color:red" class="test-colorswatch"></span><span>#F00</span></span>',
"CSS property correctly parsed");
target.innerHTML = "";
@ -48,8 +48,8 @@ function testParseCssProperty() {
});
target.appendChild(frag);
is(target.innerHTML,
'linear-gradient(to right, <span style="background-color:#F60" class="test-colorswatch"></span><span class="test-color">#F60</span> 10%, ' +
'<span style="background-color:rgba(0,0,0,1)" class="test-colorswatch"></span><span class="test-color">#000</span>)',
'linear-gradient(to right, <span data-color="#F60"><span style="background-color:#F60" class="test-colorswatch"></span><span class="test-color">#F60</span></span> 10%, ' +
'<span data-color="#000"><span style="background-color:rgba(0,0,0,1)" class="test-colorswatch"></span><span class="test-color">#000</span></span>)',
"Gradient CSS property correctly parsed");
target.innerHTML = "";
@ -69,7 +69,7 @@ function testParseHTMLAttribute() {
ok(target, "captain, we have the div");
target.appendChild(frag);
let expected = 'color:<span class="theme-color">#F00</span>; font-size: 12px; ' +
let expected = 'color:<span data-color="#F00"><span class="theme-color">#F00</span></span>; font-size: 12px; ' +
'background-image: url(\'<a href="chrome://branding/content/about-logo.png" ' +
'class="theme-link" ' +
'target="_blank">chrome://branding/content/about-logo.png</a>\')';

View File

@ -1011,6 +1011,7 @@ SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
_selectColor: function(color) {
if (this.activeSwatch) {
this.activeSwatch.style.backgroundColor = color;
this.activeSwatch.parentNode.dataset.color = color;
this.currentSwatchColor.textContent = color;
this.preview(color);
}

View File

@ -149,6 +149,7 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
this._onSelectAll = this._onSelectAll.bind(this);
this._onClick = this._onClick.bind(this);
this._onCopy = this._onCopy.bind(this);
this._onCopyColor = this._onCopyColor.bind(this);
this.styleDocument.addEventListener("copy", this._onCopy);
this.styleDocument.addEventListener("mousedown", this.focusWindow);
@ -584,6 +585,13 @@ CssHtmlTree.prototype = {
command: this._onCopy
});
// Copy color
this.menuitemCopyColor = createMenuItem(this._contextmenu, {
label: "ruleView.contextmenu.copyColor",
accesskey: "ruleView.contextmenu.copyColor.accessKey",
command: this._onCopyColor
});
// Show Original Sources
this.menuitemSources= createMenuItem(this._contextmenu, {
label: "ruleView.contextmenu.showOrigSources",
@ -619,6 +627,39 @@ CssHtmlTree.prototype = {
let accessKey = label + ".accessKey";
this.menuitemSources.setAttribute("accesskey",
CssHtmlTree.l10n(accessKey));
this.menuitemCopyColor.hidden = !this._isColorPopup();
},
/**
* A helper that determines if the popup was opened with a click to a color
* value and saves the color to this._colorToCopy.
*
* @return {Boolean}
* true if click on color opened the popup, false otherwise.
*/
_isColorPopup: function () {
this._colorToCopy = "";
let trigger = this.popupNode;
if (!trigger) {
return false;
}
let container = (trigger.nodeType == trigger.TEXT_NODE) ?
trigger.parentElement : trigger;
let isColorNode = el => el.dataset && "color" in el.dataset;
while (!isColorNode(container)) {
container = container.parentNode;
if (!container) {
return false;
}
}
this._colorToCopy = container.dataset["color"];
return true;
},
/**
@ -626,6 +667,7 @@ CssHtmlTree.prototype = {
*/
_onContextMenu: function(event) {
try {
this.popupNode = event.explicitOriginalTarget;
this.styleDocument.defaultView.focus();
this._contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
} catch(e) {
@ -660,6 +702,10 @@ CssHtmlTree.prototype = {
}
},
_onCopyColor: function() {
clipboardHelper.copyString(this._colorToCopy, this.styleDocument);
},
/**
* Copy selected text.
*
@ -751,12 +797,18 @@ CssHtmlTree.prototype = {
this.menuitemSelectAll.removeEventListener("command", this._onSelectAll);
this.menuitemSelectAll = null;
// Destroy Copy Color menuitem.
this.menuitemCopyColor.removeEventListener("command", this._onCopyColor);
this.menuitemCopyColor = null;
// Destroy the context menu.
this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate);
this._contextmenu.parentNode.removeChild(this._contextmenu);
this._contextmenu = null;
}
this.popupNode = null;
this.tooltip.stopTogglingOnHover(this.propertyContainer);
this.tooltip.destroy();

View File

@ -1079,6 +1079,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
this._onSelectAll = this._onSelectAll.bind(this);
this._onCopy = this._onCopy.bind(this);
this._onCopyColor = this._onCopyColor.bind(this);
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
this.element.addEventListener("copy", this._onCopy);
@ -1138,6 +1139,11 @@ CssRuleView.prototype = {
accesskey: "ruleView.contextmenu.copy.accessKey",
command: this._onCopy
});
this.menuitemCopyColor = createMenuItem(this._contextmenu, {
label: "ruleView.contextmenu.copyColor",
accesskey: "ruleView.contextmenu.copyColor.accessKey",
command: this._onCopyColor
});
this.menuitemSources= createMenuItem(this._contextmenu, {
label: "ruleView.contextmenu.showOrigSources",
accesskey: "ruleView.contextmenu.showOrigSources.accessKey",
@ -1261,6 +1267,7 @@ CssRuleView.prototype = {
copy = false;
}
this.menuitemCopyColor.hidden = !this._isColorPopup();
this.menuitemCopy.disabled = !copy;
let label = "ruleView.contextmenu.showOrigSources";
@ -1275,6 +1282,37 @@ CssRuleView.prototype = {
_strings.GetStringFromName(accessKey));
},
/**
* A helper that determines if the popup was opened with a click to a color
* value and saves the color to this._colorToCopy.
*
* @return {Boolean}
* true if click on color opened the popup, false otherwise.
*/
_isColorPopup: function () {
this._colorToCopy = "";
let trigger = this.doc.popupNode;
if (!trigger) {
return false;
}
let container = (trigger.nodeType == trigger.TEXT_NODE) ?
trigger.parentElement : trigger;
let isColorNode = el => el.dataset && "color" in el.dataset;
while (!isColorNode(container)) {
container = container.parentNode;
if (!container) {
return false;
}
}
this._colorToCopy = container.dataset["color"];
return true;
},
/**
* Select all text.
*/
@ -1327,6 +1365,13 @@ CssRuleView.prototype = {
}
},
/**
* Copy the most recently selected color value to clipboard.
*/
_onCopyColor: function() {
clipboardHelper.copyString(this._colorToCopy, this.styleDocument);
},
/**
* Toggle the original sources pref.
*/
@ -1400,6 +1445,10 @@ CssRuleView.prototype = {
this.menuitemCopy.removeEventListener("command", this._onCopy);
this.menuitemCopy = null;
// Destroy Copy Color menuitem.
this.menuitemCopyColor.removeEventListener("command", this._onCopyColor);
this.menuitemCopyColor = null;
this.menuitemSources.removeEventListener("command", this._onToggleOrigSources);
this.menuitemSources = null;

View File

@ -87,6 +87,8 @@ skip-if = os == "win" && debug # bug 963492
[browser_ruleview_user-agent-styles.js]
[browser_ruleview_user-agent-styles-uneditable.js]
[browser_ruleview_user-property-reset.js]
[browser_styleinspector_context-menu-copy-color_01.js]
[browser_styleinspector_context-menu-copy-color_02.js]
[browser_styleinspector_csslogic-content-stylesheets.js]
[browser_styleinspector_csslogic-inherited-properties.js]
[browser_styleinspector_csslogic-specificity.js]

View File

@ -0,0 +1,139 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test "Copy color" item of the context menu #1: Test _isColorPopup.
const TEST_COLOR = "#123ABC";
const COLOR_SELECTOR = "span[data-color]";
let test = asyncTest(function* () {
const TEST_DOC = '<html> \
<body> \
<div style="color: ' + TEST_COLOR + '; \
margin: 0px; \
background: ' + TEST_COLOR + ';"> \
Test "Copy color" context menu option \
</div> \
</body> \
</html>';
const TEST_CASES = [
{
viewName: "RuleView",
initializer: openRuleView
},
{
viewName: "ComputedView",
initializer: openComputedView
}
];
yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
for (let test of TEST_CASES) {
yield testView(test);
}
});
function* testView({viewName, initializer}) {
info("Testing " + viewName);
let {inspector, view} = yield initializer();
yield selectNode("div", inspector);
testIsColorValueNode(view);
testIsColorPopupOnAllNodes(view);
yield clearCurrentNodeSelection(inspector);
}
/**
* A function testing that isColorValueNode correctly detects nodes part of
* color values.
*/
function testIsColorValueNode(view) {
info("Testing that child nodes of color nodes are detected.");
let root = rootElement(view);
let colorNode = root.querySelector(COLOR_SELECTOR);
ok(colorNode, "Color node found");
for (let node of iterateNodes(colorNode)) {
ok(isColorValueNode(node), "Node is part of color value.");
}
}
/**
* A function testing that _isColorPopup returns a correct value for all nodes
* in the view.
*/
function testIsColorPopupOnAllNodes(view) {
let root = rootElement(view);
for (let node of iterateNodes(root)) {
testIsColorPopupOnNode(view, node);
}
}
/**
* Test result of _isColorPopup with given node.
* @param object view
* A CSSRuleView or CssHtmlTree instance.
* @param Node node
* A node to check.
*/
function testIsColorPopupOnNode(view, node) {
info("Testing node " + node);
if (view.doc) {
view.doc.popupNode = node;
}
else {
view.popupNode = node;
}
view._colorToCopy = "";
let result = view._isColorPopup();
let correct = isColorValueNode(node);
is(result, correct, "_isColorPopup returned the expected value " + correct);
is(view._colorToCopy, (correct) ? TEST_COLOR : "",
"_colorToCopy was set to the expected value");
}
/**
* Check if a node is part of color value i.e. it has parent with a 'data-color'
* attribute.
*/
function isColorValueNode(node) {
let container = (node.nodeType == node.TEXT_NODE) ?
node.parentElement : node;
let isColorNode = el => el.dataset && "color" in el.dataset;
while (!isColorNode(container)) {
container = container.parentNode;
if (!container) {
info("No color. Node is not part of color value.");
return false;
}
}
info("Found a color. Node is part of color value.");
return true;
}
/**
* A generator that iterates recursively trough all child nodes of baseNode.
*/
function* iterateNodes(baseNode) {
yield baseNode;
for (let child of baseNode.childNodes) {
yield* iterateNodes(child);
}
}
/**
* Returns the root element for the given view, rule or computed.
*/
let rootElement = view => (view.element) ? view.element : view.styleDocument;

View File

@ -0,0 +1,99 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test "Copy color" item of the context menu #2: Test that correct color is
// copied if the color changes.
const TEST_COLOR = "#123ABC";
let test = asyncTest(function* () {
const PAGE_CONTENT = [
'<style type="text/css">',
' div {',
' color: ' + TEST_COLOR + ';',
' }',
'</style>',
'<div>Testing the color picker tooltip!</div>'
].join("\n");
yield addTab("data:text/html;charset=utf8,Test context menu Copy color");
content.document.body.innerHTML = PAGE_CONTENT;
let {inspector, view} = yield openRuleView();
yield testCopyToClipboard(inspector, view);
yield testManualEdit(inspector, view);
yield testColorPickerEdit(inspector, view);
});
function* testCopyToClipboard(inspector, view) {
info("Testing that color is copied to clipboard");
yield selectNode("div", inspector);
let win = view.doc.defaultView;
let element = getRuleViewProperty(view, "div", "color").valueSpan
.querySelector(".ruleview-colorswatch");
let popup = once(view._contextmenu, "popupshown");
EventUtils.synthesizeMouseAtCenter(element, {button: 2, type: "contextmenu"}, win);
yield popup;
ok(!view.menuitemCopyColor.hidden, "Copy color is visible");
yield waitForClipboard(() => view.menuitemCopyColor.click(), TEST_COLOR);
view._contextmenu.hidePopup();
}
function* testManualEdit(inspector, view) {
info("Testing manually edited colors");
yield selectNode("div", inspector);
let {valueSpan} = getRuleViewProperty(view, "div", "color");
let newColor = "#C9184E"
let editor = yield focusEditableField(valueSpan);
info("Typing new value");
let input = editor.input;
let onBlur = once(input, "blur");
for (let ch of newColor + ";"){
EventUtils.sendChar(ch, view.doc.defaultView);
}
yield onBlur;
yield wait(1);
let colorValue = getRuleViewProperty(view, "div", "color").valueSpan.firstChild;
is(colorValue.dataset.color, newColor, "data-color was updated");
view.doc.popupNode = colorValue;
view._isColorPopup();
is(view._colorToCopy, newColor, "_colorToCopy has the new value");
}
function* testColorPickerEdit(inspector, view) {
info("Testing colors edited via color picker");
yield selectNode("div", inspector);
let swatch = getRuleViewProperty(view, "div", "color").valueSpan
.querySelector(".ruleview-colorswatch");
info("Opening the color picker");
let picker = view.colorPicker;
let onShown = picker.tooltip.once("shown");
swatch.click();
yield onShown;
let rgbaColor = [83, 183, 89, 1];
let rgbaColorText = "rgba(83, 183, 89, 1)";
yield simulateColorPickerChange(picker, rgbaColor);
is(swatch.parentNode.dataset.color, rgbaColorText, "data-color was updated");
view.doc.popupNode = swatch;
view._isColorPopup();
is(view._colorToCopy, rgbaColorText, "_colorToCopy has the new value");
}

View File

@ -73,7 +73,7 @@ function test() {
name: "background-color",
value: "transparent",
test: fragment => {
is(countAll(fragment), 1);
is(countAll(fragment), 2);
is(countColors(fragment), 1);
is(fragment.textContent, "transparent");
}
@ -98,7 +98,7 @@ function test() {
name: "border",
value: "80em dotted pink",
test: fragment => {
is(countAll(fragment), 1);
is(countAll(fragment), 2);
is(countColors(fragment), 1);
is(getColor(fragment), "pink");
}
@ -119,7 +119,7 @@ function test() {
is(countUrls(fragment), 1);
is(getColor(fragment), "red");
is(getUrl(fragment), "test.png");
is(countAll(fragment), 2);
is(countAll(fragment), 3);
}
},
{
@ -130,7 +130,7 @@ function test() {
is(countUrls(fragment), 1);
is(getColor(fragment), "blue");
is(getUrl(fragment), "test.png");
is(countAll(fragment), 2);
is(countAll(fragment), 3);
}
},
{
@ -163,7 +163,7 @@ function test() {
name: "background",
value: "linear-gradient(to right, rgba(183,222,237,1) 0%, rgba(33,180,226,1) 30%, rgba(31,170,217,.5) 44%, #F06 75%, red 100%)",
test: fragment => {
is(countAll(fragment), 5);
is(countAll(fragment), 10);
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
is(allSwatches.length, 5);
is(allSwatches[0].textContent, "rgba(183,222,237,1)");
@ -177,7 +177,7 @@ function test() {
name: "background",
value: "-moz-radial-gradient(center 45deg, circle closest-side, orange 0%, red 100%)",
test: fragment => {
is(countAll(fragment), 2);
is(countAll(fragment), 4);
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
is(allSwatches.length, 2);
is(allSwatches[0].textContent, "orange");
@ -188,7 +188,7 @@ function test() {
name: "background",
value: "white url(http://test.com/wow_such_image.png) no-repeat top left",
test: fragment => {
is(countAll(fragment), 2);
is(countAll(fragment), 3);
is(countUrls(fragment), 1);
is(countColors(fragment), 1);
}
@ -213,7 +213,7 @@ function test() {
name: "background",
value: "red url( \"http://wow.com/cool/../../../you're(doingit)wrong\" ) repeat center",
test: fragment => {
is(countAll(fragment), 2);
is(countAll(fragment), 3);
is(countColors(fragment), 1);
is(getUrl(fragment), "http://wow.com/cool/../../../you're(doingit)wrong");
}

View File

@ -56,11 +56,13 @@
tabindex="0"/>
</toolbar>
<splitter class="devtools-horizontal-splitter"/>
<box id="web-audio-content-pane" flex="1">
<box id="web-audio-content-pane"
class="devtools-responsive-container"
flex="1">
<hbox flex="1">
<box id="web-audio-graph" class="devtools-responsive-container" flex="1">
<box id="web-audio-graph" flex="1">
<vbox flex="1">
<svg id="graph-svg" flex="1" viewBox="0 0 1000 500"
<svg id="graph-svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph-target" transform="translate(20,20)"/>

View File

@ -1396,7 +1396,7 @@ mediaResetDesc=Stop emulating a CSS media type
# injectFailed) These strings describe the 'inject' commands and all available
# parameters.
injectDesc=Inject common libraries into the page
injectManual=Inject common libraries into the content of the page which can also be accessed from the Firefox console.
injectManual2=Inject common libraries into the content of the page which can also be accessed from the console.
injectLibraryDesc=Select the library to inject or enter a valid script URI to inject
injectLoaded=%1$S loaded
injectFailed=Failed to load %1$S - Invalid URI

View File

@ -86,3 +86,11 @@ description {
font-size: 1.25rem;
line-height: 22px;
}
#popupPolicyRow {
/* Override styles from
browser/themes/osx/preferences/preferences.css */
margin-bottom: 0 !important;
padding-bottom: 0 !important;
border-bottom: none;
}

View File

@ -134,9 +134,7 @@ caption {
-moz-box-align: center;
}
#popupPolicyRow,
#enableSoftwareInstallRow,
#enableImagesRow {
#popupPolicyRow {
margin-bottom: 4px !important;
padding-bottom: 4px !important;
border-bottom: 1px solid #ccc;

View File

@ -139,7 +139,9 @@ panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .pan
.panel-subview-header,
.subviewbutton.panel-subview-footer {
padding: 12px;
box-sizing: border-box;
min-height: 41px;
padding: 11px 12px;
}
.panel-subview-header {
@ -490,8 +492,9 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
#PanelUI-customize,
#PanelUI-quit {
margin: 0;
padding: 10px 0;
min-height: 2em;
padding: 11px 0;
box-sizing: border-box;
min-height: 40px;
-moz-appearance: none;
box-shadow: none;
border: none;

View File

@ -40,8 +40,8 @@
/* Context Graph */
svg {
position: fixed;
overflow: hidden;
-moz-box-flex: 1;
}
/* Edges in graph */
@ -111,12 +111,6 @@ text {
transition: opacity .5s ease-out 0s;
}
@media (min-resolution: 2dppx) {
.web-audio-inspector .error {
background-image: url(alerticon-warning@2x.png);
}
}
#inspector-pane-toggle {
background: none;
box-shadow: none;
@ -151,4 +145,28 @@ text {
#inspector-pane-toggle:active {
-moz-image-region: rect(0px,64px,32px,32px);
}
}
.web-audio-inspector .error {
background-image: url(alerticon-warning@2x.png);
}
}
/**
* Responsive Styles
* `.devtools-responsive-container` takes care of most of
* the changing of host types.
*/
@media (max-width: 700px) {
/**
* Override the inspector toggle so it's always open
* in the portrait view, with the toggle button hidden.
*/
#inspector-pane-toggle {
display: none;
}
#web-audio-inspector {
margin-left: 0px !important;
margin-right: 0px !important;
}
}

View File

@ -324,19 +324,61 @@ case "$target" in
ANDROID_SDK="${android_sdk}"
ANDROID_SDK_ROOT="${android_sdk_root}"
if test -e "${ANDROID_SDK_ROOT}/extras/android/compatibility/v4/android-support-v4.jar" ; then
ANDROID_COMPAT_LIB="${ANDROID_SDK_ROOT}/extras/android/compatibility/v4/android-support-v4.jar"
AC_MSG_CHECKING([for compat library dirs])
if test -e "${android_sdk_root}/extras/android/compatibility/v4/android-support-v4.jar" ; then
ANDROID_COMPAT_DIR_BASE="${android_sdk_root}/extras/android/compatibility";
else
ANDROID_COMPAT_LIB="${ANDROID_SDK_ROOT}/extras/android/support/v4/android-support-v4.jar";
ANDROID_COMPAT_DIR_BASE="${android_sdk_root}/extras/android/support";
fi
AC_MSG_RESULT([$ANDROID_COMPAT_DIR_BASE])
ANDROID_TOOLS="${android_tools}"
ANDROID_PLATFORM_TOOLS="${android_platform_tools}"
ANDROID_BUILD_TOOLS="${android_build_tools}"
AC_SUBST(ANDROID_SDK_ROOT)
AC_SUBST(ANDROID_SDK)
ANDROID_COMPAT_LIB=$ANDROID_COMPAT_DIR_BASE/v4/android-support-v4.jar
AC_MSG_CHECKING([for v4 compat library])
AC_SUBST(ANDROID_COMPAT_LIB)
if ! test -e $ANDROID_COMPAT_LIB ; then
AC_MSG_ERROR([You must download the Android support library when targeting Android. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_COMPAT_LIB)])
AC_MSG_ERROR([You must download the Android v4 support library when targeting Android. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_COMPAT_LIB)])
fi
AC_MSG_RESULT([$ANDROID_COMPAT_LIB])
if test -n "$MOZ_NATIVE_DEVICES" ; then
AC_SUBST(MOZ_NATIVE_DEVICES)
AC_MSG_CHECKING([for google play services])
GOOGLE_PLAY_SERVICES_LIB="${ANDROID_SDK_ROOT}/extras/google/google_play_services/libproject/google-play-services_lib/libs/google-play-services.jar"
GOOGLE_PLAY_SERVICES_RES="${ANDROID_SDK_ROOT}/extras/google/google_play_services/libproject/google-play-services_lib/res"
AC_SUBST(GOOGLE_PLAY_SERVICES_LIB)
AC_SUBST(GOOGLE_PLAY_SERVICES_RES)
if ! test -e $GOOGLE_PLAY_SERVICES_LIB ; then
AC_MSG_ERROR([You must download Google Play Services to build with native video casting support enabled. Run the Android SDK tool and install Google Play Services under Extras. See http://developer.android.com/google/play-services/setup.html for more info. (looked for $GOOGLE_PLAY_SERVICES_LIB) ])
fi
AC_MSG_RESULT([$GOOGLE_PLAY_SERVICES_LIB])
ANDROID_APPCOMPAT_LIB="$ANDROID_COMPAT_DIR_BASE/v7/appcompat/libs/android-support-v7-appcompat.jar"
ANDROID_APPCOMPAT_RES="$ANDROID_COMPAT_DIR_BASE/v7/appcompat/res"
AC_MSG_CHECKING([for v7 appcompat library])
if ! test -e $ANDROID_APPCOMPAT_LIB ; then
AC_MSG_ERROR([You must download the v7 app compat Android support library when targeting Android with native video casting support enabled. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_APPCOMPAT_LIB)])
fi
AC_MSG_RESULT([$ANDROID_APPCOMPAT_LIB])
AC_SUBST(ANDROID_APPCOMPAT_LIB)
AC_SUBST(ANDROID_APPCOMPAT_RES)
ANDROID_MEDIAROUTER_LIB="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/libs/android-support-v7-mediarouter.jar"
ANDROID_MEDIAROUTER_RES="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/res"
AC_MSG_CHECKING([for v7 mediarouter library])
if ! test -e $ANDROID_MEDIAROUTER_LIB ; then
AC_MSG_ERROR([You must download the v7 media router Android support library when targeting Android with native video casting support enabled. Run the Android SDK tool and install Android Support Library under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_MEDIAROUTER_LIB)])
fi
AC_MSG_RESULT([$ANDROID_MEDIAROUTER_LIB])
AC_SUBST(ANDROID_MEDIAROUTER_LIB)
AC_SUBST(ANDROID_MEDIAROUTER_RES)
fi
MOZ_PATH_PROG(ZIPALIGN, zipalign, :, [$ANDROID_TOOLS])

View File

@ -95,7 +95,7 @@ GENERATED_DIRS += $(dir-tests)
# being linked against them. This is a best effort to avoid getting
# out of sync with base's build config.
JARS_DIR := $(DEPTH)/mobile/android/base
JAVA_BOOTCLASSPATH := $(JAVA_BOOTCLASSPATH):$(subst $(NULL) ,:,$(wildcard $(JARS_DIR)/*.jar))
JAVA_BOOTCLASSPATH := $(JAVA_BOOTCLASSPATH):$(subst $(NULL) ,:,$(wildcard $(JARS_DIR)/*.jar)):$(ANDROID_COMPAT_LIB)
# We also want to re-compile classes.dex when the associated base
# content changes.
classes.dex: $(wildcard $(JARS_DIR)/*.jar)

View File

@ -2,16 +2,12 @@
# 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/.
# Ensure JAVA_CLASSPATH and ANDROID_SDK are defined before including this file.
# Ensure ANDROID_SDK is defined before including this file.
# We use common android defaults for boot class path and java version.
ifndef ANDROID_SDK
$(error ANDROID_SDK must be defined before including android-common.mk)
endif
ifndef JAVA_CLASSPATH
$(error JAVA_CLASSPATH must be defined before including android-common.mk)
endif
# DEBUG_JARSIGNER always debug signs.
DEBUG_JARSIGNER=$(PYTHON) $(abspath $(topsrcdir)/mobile/android/debug_sign_tool.py) \
--keytool=$(KEYTOOL) \
@ -20,7 +16,7 @@ DEBUG_JARSIGNER=$(PYTHON) $(abspath $(topsrcdir)/mobile/android/debug_sign_tool.
# For Android, this defaults to $(ANDROID_SDK)/android.jar
ifndef JAVA_BOOTCLASSPATH
JAVA_BOOTCLASSPATH = $(ANDROID_SDK)/android.jar:$(ANDROID_COMPAT_LIB)
JAVA_BOOTCLASSPATH = $(ANDROID_SDK)/android.jar
endif
# For Android, we default to 1.5
@ -31,7 +27,7 @@ endif
JAVAC_FLAGS = \
-target $(JAVA_VERSION) \
-source $(JAVA_VERSION) \
-classpath $(JAVA_CLASSPATH) \
$(if $(JAVA_CLASSPATH),-classpath $(JAVA_CLASSPATH),) \
-bootclasspath $(JAVA_BOOTCLASSPATH) \
-encoding UTF8 \
-g:source,lines \

View File

@ -14,3 +14,4 @@ skip = false
[test_touchcaret.py]
b2g = false ; Bug 1020261
[test_selectioncarets.py]
skip-if = os == "win" #perma-fail on Windows

View File

@ -194,6 +194,10 @@ pref("extensions.minCompatibleAppVersion", "11.0");
pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
pref("extensions.hotfix.id", "firefox-android-hotfix@mozilla.org");
pref("extensions.hotfix.cert.checkAttributes", true);
pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35:22:9E:11:C9:A7:31:04:49:A1:AA");
/* preferences for the Get Add-ons pane */
pref("extensions.getAddons.cache.enabled", true);
pref("extensions.getAddons.maxResults", 15);

View File

@ -83,6 +83,11 @@
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
#ifdef GOOGLE_PLAY_SERVICES
<!-- This resources comes from Google Play Services. Required for casting support. -->
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
#endif
<!-- If the windowSoftInputMode adjust* flag changes below, the
setSoftInputMode call in BrowserSearch#onStop must also be updated. -->
<activity android:name=".App"

View File

@ -487,7 +487,7 @@ abstract public class BrowserApp extends GeckoApp
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
}
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());
((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
@Override
public boolean onInterceptMotionEvent(View view, MotionEvent event) {
@ -1990,11 +1990,17 @@ abstract public class BrowserApp extends GeckoApp
mBrowserSearch.setUserVisibleHint(false);
}
private class HideTabsTouchListener implements TouchEventInterceptor {
/**
* Hides certain UI elements (e.g. button toast, tabs tray) when the
* user touches the main layout.
*/
private class HideOnTouchListener implements TouchEventInterceptor {
private boolean mIsHidingTabs = false;
@Override
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
getButtonToast().hide(false, ButtonToast.ReasonHidden.TOUCH_OUTSIDE);
// We need to account for scroll state for the touched view otherwise
// tapping on an "empty" part of the view will still be considered a
// valid touch event.

View File

@ -59,7 +59,23 @@ GARBAGE += \
GARBAGE_DIRS += classes db jars res sync services generated
JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
JAVA_BOOTCLASSPATH = \
$(ANDROID_SDK)/android.jar \
$(ANDROID_COMPAT_LIB) \
$(NULL)
JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH)))
# If native devices are enabled, add Google Play Services and some of the v7 compat libraries
ifdef MOZ_NATIVE_DEVICES
JAVA_CLASSPATH += \
$(GOOGLE_PLAY_SERVICES_LIB) \
$(ANDROID_MEDIAROUTER_LIB) \
$(ANDROID_APPCOMPAT_LIB) \
$(NULL)
endif
JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
ALL_JARS = \
gecko-R.jar \
@ -83,9 +99,16 @@ include $(topsrcdir)/config/config.mk
# because Android resource classes must be compiled together in order to avoid overlapping resource
# indices.
library_jars = \
$(JAVA_CLASSPATH) \
$(JAVA_BOOTCLASSPATH) \
$(NULL)
library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
classes.dex: .proguard.deps
$(REPORT_BUILD)
$(DX) --dex --output=classes.dex jars-proguarded $(ANDROID_COMPAT_LIB)
$(DX) --dex --output=classes.dex jars-proguarded $(subst :, ,$(ANDROID_COMPAT_LIB):$(JAVA_CLASSPATH))
ifdef MOZ_DISABLE_PROGUARD
PROGUARD_PASSES=0
@ -113,7 +136,7 @@ endif
-optimizationpasses $(PROGUARD_PASSES) \
-injars $(subst ::,:,$(subst $(NULL) ,:,$(strip $(ALL_JARS)))) \
-outjars jars-proguarded \
-libraryjars $(ANDROID_SDK)/android.jar:$(ANDROID_COMPAT_LIB)
-libraryjars $(library_jars)
CLASSES_WITH_JNI= \
org.mozilla.gecko.ANRReporter \
@ -141,16 +164,15 @@ ANNOTATION_PROCESSOR_JAR_FILES := $(DEPTH)/build/annotationProcessors/annotation
GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES)
GeneratedJNIWrappers.cpp: $(ALL_JARS)
$(JAVA) -classpath gecko-mozglue.jar:$(JAVA_BOOTCLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
$(JAVA) -classpath gecko-mozglue.jar:$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
gecko_package_dir = generated/org/mozilla/gecko
# Like generated/org/mozilla/fennec_$USERID.
android_package_dir = $(addprefix generated/,$(subst .,/,$(ANDROID_PACKAGE_NAME)))
# These _PP_JAVAFILES are specified in moz.build and defined in
# backend.mk, which is included by config.mk. Therefore this needs to
# be defined after config.mk is included.
PP_JAVAFILES := $(filter-out $(gecko_package_dir)/R.java,$(gecko-mozglue_PP_JAVAFILES) $(gecko-browser_PP_JAVAFILES))
PP_JAVAFILES := $(filter-out generated/org/mozilla/gecko/R.java,$(gecko-mozglue_PP_JAVAFILES) $(gecko-browser_PP_JAVAFILES))
manifest := \
AndroidManifest.xml.in \
@ -163,9 +185,9 @@ PP_TARGETS += manifest
# generates these files into generated/org/mozilla/gecko for
# consumption by the build system and IDEs.
preprocessed := $(addsuffix .in,$(subst $(gecko_package_dir)/,,$(filter $(gecko_package_dir)/%,$(PP_JAVAFILES))))
preprocessed := $(addsuffix .in,$(subst generated/org/mozilla/gecko/,,$(filter generated/org/mozilla/gecko/%,$(PP_JAVAFILES))))
preprocessed_PATH := $(gecko_package_dir)
preprocessed_PATH := generated/org/mozilla/gecko
preprocessed_KEEP_PATH := 1
PP_TARGETS += preprocessed
@ -222,6 +244,7 @@ $(ANDROID_GENERATED_RESFILES): $(call mkdir_deps,$(sort $(dir $(ANDROID_GENERATE
$(TOUCH) $@
$(MAKE) -C locales
# This .deps pattern saves an invocation of the sub-Make: the single
# invocation generates both strings.xml and suggestedsites.json. The
# trailing semi-colon defines an empty recipe: defining no recipe at
@ -268,7 +291,24 @@ geckoview_resources.zip: $(all_resources) $(GLOBAL_DEPS)
# Make to treat the target differently, in a way that defeats our
# dependencies.
$(gecko_package_dir)/R.java: .aapt.deps ;
generated/org/mozilla/gecko/R.java: .aapt.deps ;
# If native devices are enabled, add Google Play Services, build their resources
generated/android/support/v7/appcompat/R.java: .aapt.deps ;
generated/android/support/v7/mediarouter/R.java: .aapt.deps ;
generated/com/google/android/gms/R.java: .aapt.deps ;
ifdef MOZ_NATIVE_DEVICES
extra_packages += android.support.v7.appcompat
extra_res_dirs += $(ANDROID_APPCOMPAT_RES)
extra_packages += android.support.v7.mediarouter
extra_res_dirs += $(ANDROID_MEDIAROUTER_RES)
extra_packages += com.google.android.gms
extra_res_dirs += $(GOOGLE_PLAY_SERVICES_RES)
endif
gecko.ap_: .aapt.deps ;
R.txt: .aapt.deps ;
@ -289,6 +329,8 @@ gecko-nodeps/R.txt: .aapt.nodeps ;
ANDROID_AAPT_IGNORE := !.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:\#*:*.rej:*.orig
extra_packages := $(subst $(NULL) ,:,$(strip $(extra_packages)))
# 1: target file.
# 2: dependencies.
# 3: name of ap_ file to write.
@ -301,10 +343,15 @@ ANDROID_AAPT_IGNORE := !.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc
define aapt_command
$(1): $$(call mkdir_deps,$(filter-out ./,$(dir $(3) $(4) $(5)))) $(2)
@$$(TOUCH) $$@
$$(AAPT) package -f -M AndroidManifest.xml -I $$(ANDROID_SDK)/android.jar \
$$(AAPT) package -f -m \
-M AndroidManifest.xml \
-I $(ANDROID_SDK)/android.jar \
--auto-add-overlay \
$$(addprefix -S ,$$(ANDROID_RES_DIRS)) \
--custom-package org.mozilla.gecko --non-constant-id \
$(if $(extra_res_dirs),$$(addprefix -S ,$$(extra_res_dirs)),) \
$(if $(extra_packages),--extra-packages $$(extra_packages),) \
--custom-package org.mozilla.gecko \
--non-constant-id \
-F $(3) \
-J $(4) \
--output-text-symbols $(5) \
@ -319,12 +366,12 @@ endef
# toolkit/mozapps/installer/packager.mk.
# .aapt.deps: $(all_resources)
$(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,$(gecko_package_dir)/,./))
$(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,generated/,./))
# .aapt.nodeps: $(CURDIR)/AndroidManifest.xml FORCE
$(eval $(call aapt_command,.aapt.nodeps,$(CURDIR)/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
fennec_ids.txt: $(gecko_package_dir)/R.java fennec-ids-generator.py
fennec_ids.txt: generated/org/mozilla/gecko/R.java fennec-ids-generator.py
$(PYTHON) $(topsrcdir)/mobile/android/base/fennec-ids-generator.py -i $< -o $@
# Override the Java settings with some specific android settings

View File

@ -153,6 +153,10 @@ public class BrowserDB {
sSuggestedSites = suggestedSites;
}
public static boolean hideSuggestedSite(String url) {
return sSuggestedSites.hideSite(url);
}
public static void invalidateCachedState() {
sDb.invalidateCachedState();
}

View File

@ -10,18 +10,21 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoSharedPrefs;
@ -30,6 +33,7 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.util.RawResource;
import org.mozilla.gecko.util.ThreadUtils;
/**
* {@code SuggestedSites} provides API to get a list of locale-specific
@ -47,11 +51,16 @@ import org.mozilla.gecko.util.RawResource;
* The default list of suggested sites is stored in a raw Android
* resource ({@code R.raw.suggestedsites}) which is dynamically
* generated at build time for each target locale.
*
* Changes to the list of suggested sites are saved in SharedPreferences.
*/
@RobocopTarget
public class SuggestedSites {
private static final String LOGTAG = "GeckoSuggestedSites";
// SharedPreference key for suggested sites that should be hidden.
public static final String PREF_SUGGESTED_SITES_HIDDEN = "suggestedSites.hidden";
private static final String[] COLUMNS = new String[] {
BrowserContract.SuggestedSites._ID,
BrowserContract.SuggestedSites.URL,
@ -88,6 +97,7 @@ public class SuggestedSites {
private final Context context;
private Map<String, Site> cachedSites;
private Locale cachedLocale;
private Set<String> cachedBlacklist;
public SuggestedSites(Context appContext) {
context = appContext;
@ -155,7 +165,7 @@ public class SuggestedSites {
return prefs.getBoolean(GeckoPreferences.PREFS_SUGGESTED_SITES, true);
}
private Site getSiteForUrl(String url) {
private synchronized Site getSiteForUrl(String url) {
if (cachedSites == null) {
return null;
}
@ -199,7 +209,7 @@ public class SuggestedSites {
* @param locale the target locale.
* @param excludeUrls list of URLs to be excluded from the list.
*/
public Cursor get(int limit, Locale locale, List<String> excludeUrls) {
public synchronized Cursor get(int limit, Locale locale, List<String> excludeUrls) {
final MatrixCursor cursor = new MatrixCursor(COLUMNS);
// Return an empty cursor if suggested sites have been
@ -219,6 +229,8 @@ public class SuggestedSites {
return cursor;
}
excludeUrls = includeBlacklist(excludeUrls);
final int sitesCount = cachedSites.size();
Log.d(LOGTAG, "Number of suggested sites: " + sitesCount);
@ -257,4 +269,83 @@ public class SuggestedSites {
final Site site = getSiteForUrl(url);
return (site != null ? site.bgColor : null);
}
}
private Set<String> loadBlacklist() {
Log.d(LOGTAG, "Loading blacklisted suggested sites from SharedPreferences.");
final Set<String> blacklist = new HashSet<String>();
final SharedPreferences preferences = GeckoSharedPrefs.forProfile(context);
final String sitesString = preferences.getString(PREF_SUGGESTED_SITES_HIDDEN, null);
if (sitesString != null) {
for (String site : sitesString.trim().split(" ")) {
blacklist.add(Uri.decode(site));
}
}
return blacklist;
}
private List<String> includeBlacklist(List<String> originalList) {
if (cachedBlacklist == null) {
cachedBlacklist = loadBlacklist();
}
if (cachedBlacklist.isEmpty()) {
return originalList;
}
if (originalList == null) {
originalList = new ArrayList<String>();
}
originalList.addAll(cachedBlacklist);
return originalList;
}
/**
* Blacklist a suggested site so it will no longer be returned as a suggested site.
* This method should only be called from a background thread because it may write
* to SharedPreferences.
*
* Urls that are not Suggested Sites are ignored.
*
* @param url String url of site to blacklist
* @return true is blacklisted, false otherwise
*/
public synchronized boolean hideSite(String url) {
ThreadUtils.assertNotOnUiThread();
if (cachedSites == null) {
refresh();
if (cachedSites == null) {
Log.w(LOGTAG, "Could not load suggested sites!");
return false;
}
}
if (cachedSites.containsKey(url)) {
if (cachedBlacklist == null) {
cachedBlacklist = loadBlacklist();
}
// Check if site has already been blacklisted, just in case.
if (!cachedBlacklist.contains(url)) {
saveToBlacklist(url);
cachedBlacklist.add(url);
return true;
}
}
return false;
}
private void saveToBlacklist(String url) {
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
final String prefString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN, "");
final String siteString = prefString.concat(" " + Uri.encode(url));
prefs.edit().putString(PREF_SUGGESTED_SITES_HIDDEN, siteString).commit();
}
}

View File

@ -19,6 +19,7 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
@ -333,6 +334,9 @@ abstract class HomeFragment extends Fragment {
if (mPosition > -1) {
BrowserDB.unpinSite(cr, mPosition);
if (BrowserDB.hideSuggestedSite(mUrl)) {
cr.notifyChange(SuggestedSites.CONTENT_URI, null);
}
}
BrowserDB.removeBookmarksWithURL(cr, mUrl);

View File

@ -16,6 +16,13 @@ resjar.sources = []
resjar.generated_sources += [
'org/mozilla/gecko/R.java',
]
if CONFIG['MOZ_NATIVE_DEVICES']:
resjar.generated_sources += ['com/google/android/gms/R.java']
DEFINES["GOOGLE_PLAY_SERVICES"] = 1
resjar.generated_sources += ['android/support/v7/appcompat/R.java']
resjar.generated_sources += ['android/support/v7/mediarouter/R.java']
resjar.javac_flags += ['-Xlint:all']
mgjar = add_java_jar('gecko-mozglue')
@ -470,6 +477,12 @@ gbjar.extra_jars = [
'sync-thirdparty.jar',
'websockets.jar',
]
if CONFIG['MOZ_NATIVE_DEVICES']:
gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_MEDIAROUTER_LIB']]
gbjar.extra_jars += [CONFIG['GOOGLE_PLAY_SERVICES_LIB']]
gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough']
spjar = add_java_jar('squareup-picasso')

View File

@ -5,6 +5,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/toast_button_background" />
<solid android:color="@color/toast_background" />
<corners android:radius="@dimen/toast_button_corner_radius" />
</shape>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<!-- Button toast colors. -->
<color name="toast_button_background">#FF656565</color>
<color name="toast_button_pressed">#FFB1B1B1</color>
</resources>

View File

@ -14,8 +14,8 @@
<item name="android:background">@null</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
<item name="android:paddingTop">18dp</item>
<item name="android:paddingBottom">18dp</item>
<item name="android:paddingTop">11dp</item>
<item name="android:paddingBottom">11dp</item>
</style>
<style name="ToastDivider" parent="ToastDividerBase">

View File

@ -108,8 +108,9 @@
<color name="remote_tabs_setup_button_background_hit">#D95300</color>
<!-- Button toast colors. -->
<color name="toast_button_background">#FF2A2A2A</color>
<color name="toast_button_pressed">#FF3E6784</color>
<color name="toast_background">#DD363B40</color>
<color name="toast_button_background">#00000000</color>
<color name="toast_button_pressed">#DD2C3136</color>
<color name="toast_button_divider">#FFD1D5DA</color>
<color name="toast_button_text">#FFFFFFFF</color>
</resources>

View File

@ -611,8 +611,8 @@
<item name="android:background">@null</item>
<item name="android:paddingLeft">12dp</item>
<item name="android:paddingRight">12dp</item>
<item name="android:paddingTop">20dp</item>
<item name="android:paddingBottom">20dp</item>
<item name="android:paddingTop">11dp</item>
<item name="android:paddingBottom">11dp</item>
</style>
<style name="ToastDividerBase">

View File

@ -42,6 +42,7 @@ public class ButtonToast {
public enum ReasonHidden {
CLICKED,
TOUCH_OUTSIDE,
TIMEOUT,
REPLACED,
STARTUP

View File

@ -121,10 +121,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
Services.scriptloader.loadSubScript(script, sandbox);
return sandbox[name];
});
notifications.forEach(function (aNotification) {
Services.obs.addObserver(function(s, t, d) {
window[name].observe(s, t, d)
}, aNotification, false);
let observer = (s, t, d) => {
Services.obs.removeObserver(observer, t);
Services.obs.addObserver(window[name], t, false);
window[name].observe(s, t, d); // Explicitly notify new observer
};
notifications.forEach((notification) => {
Services.obs.addObserver(observer, notification, false);
});
});
@ -135,10 +138,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
].forEach(module => {
let [name, notifications, resource] = module;
XPCOMUtils.defineLazyModuleGetter(this, name, resource);
let observer = (s, t, d) => {
Services.obs.removeObserver(observer, t);
Services.obs.addObserver(this[name], t, false);
this[name].observe(s, t, d); // Explicitly notify new observer
};
notifications.forEach(notification => {
Services.obs.addObserver((s,t,d) => {
this[name].observe(s,t,d)
}, notification, false);
Services.obs.addObserver(observer, notification, false);
});
});

View File

@ -194,6 +194,8 @@
*;
}
-keep class **.R$*
# Disable obfuscation because it makes exception stack traces more difficult to read.
-dontobfuscate

View File

@ -69,3 +69,6 @@ MOZ_LOCALE_SWITCHER=1
# Enable second screen and casting support for external devices.
MOZ_DEVICES=1
# Enable second screen using native Android libraries
MOZ_NATIVE_DEVICES=

View File

@ -7,6 +7,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.test.mock.MockResources;
import android.test.RenamingDelegatingContext;
@ -20,7 +21,6 @@ import java.util.Locale;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.R;
@ -28,7 +28,6 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.SuggestedSites;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.util.RawResource;
public class TestSuggestedSites extends BrowserTestCase {
private static class TestContext extends RenamingDelegatingContext {
@ -192,6 +191,44 @@ public class TestSuggestedSites extends BrowserTestCase {
c.close();
}
public void testHiddenSites() {
resources.setSuggestedSitesResource(generateSites(6));
List<String> visibleUrls = new ArrayList<String>(3);
visibleUrls.add("url3");
visibleUrls.add("url4");
visibleUrls.add("url5");
List<String> hiddenUrls = new ArrayList<String>(3);
hiddenUrls.add("url0");
hiddenUrls.add("url1");
hiddenUrls.add("url2");
// Add mocked hidden sites to SharedPreferences.
StringBuilder hiddenUrlBuilder = new StringBuilder();
for (String s : hiddenUrls) {
hiddenUrlBuilder.append(" ");
hiddenUrlBuilder.append(Uri.encode(s));
}
final String hiddenPref = hiddenUrlBuilder.toString();
GeckoSharedPrefs.forProfile(context).edit()
.putString(SuggestedSites.PREF_SUGGESTED_SITES_HIDDEN, hiddenPref)
.commit();
Cursor c = new SuggestedSites(context).get(DEFAULT_LIMIT);
assertEquals(Math.min(3, DEFAULT_LIMIT), c.getCount());
c.moveToPosition(-1);
while (c.moveToNext()) {
String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
assertFalse(hiddenUrls.contains(url));
assertTrue(visibleUrls.contains(url));
}
c.close();
}
public void testDisabledState() {
resources.setSuggestedSitesResource(generateSites(3));

View File

@ -41,7 +41,7 @@ want to do then dive in!
.. toctree::
:maxdepth: 2
manifestdestiny
manifestparser
gettinginfo
setuprunning
mozhttpd

View File

@ -33,6 +33,7 @@ class LocalAppNotFoundError(VersionError):
INI_DATA_MAPPING = (('application', 'App'), ('platform', 'Build'))
class Version(mozlog.LoggingMixin):
def __init__(self):
@ -62,6 +63,7 @@ class Version(mozlog.LoggingMixin):
self._info['application_display_name'] = \
self._info.get('application_name')
class LocalFennecVersion(Version):
def __init__(self, path, **kwargs):
@ -78,6 +80,7 @@ class LocalFennecVersion(Version):
else:
self.warn('Unable to find %s' % filename)
class LocalVersion(Version):
def __init__(self, binary, **kwargs):
@ -168,11 +171,12 @@ class LocalB2GVersion(B2GVersion):
class RemoteB2GVersion(B2GVersion):
def __init__(self, sources=None, dm_type='adb', host=None, **kwargs):
def __init__(self, sources=None, dm_type='adb', host=None,
device_serial=None, **kwargs):
B2GVersion.__init__(self, sources, **kwargs)
if dm_type == 'adb':
dm = mozdevice.DeviceManagerADB()
dm = mozdevice.DeviceManagerADB(deviceSerial=device_serial)
elif dm_type == 'sut':
if not host:
raise Exception('A host for SUT must be supplied.')
@ -218,7 +222,8 @@ class RemoteB2GVersion(B2GVersion):
self._info[desired_props[key]] = value
def get_version(binary=None, sources=None, dm_type=None, host=None):
def get_version(binary=None, sources=None, dm_type=None, host=None,
device_serial=None):
"""
Returns the application version information as a dict. You can specify
a path to the binary of the application or an Android APK file (to get
@ -231,6 +236,7 @@ def get_version(binary=None, sources=None, dm_type=None, host=None):
:param sources: Path to the sources.xml file (Firefox OS)
:param dm_type: Device manager type. Must be 'adb' or 'sut' (Firefox OS)
:param host: Host address of remote Firefox OS instance (SUT)
:param device_serial: Serial identifier of Firefox OS device (ADB)
"""
try:
if binary and zipfile.is_zipfile(binary) and 'AndroidManifest.xml' in \
@ -241,7 +247,8 @@ def get_version(binary=None, sources=None, dm_type=None, host=None):
if version._info.get('application_name') == 'B2G':
version = LocalB2GVersion(binary, sources=sources)
except LocalAppNotFoundError:
version = RemoteB2GVersion(sources=sources, dm_type=dm_type, host=host)
version = RemoteB2GVersion(sources=sources, dm_type=dm_type, host=host,
device_serial=device_serial)
return version._info
@ -253,13 +260,17 @@ def cli(args=sys.argv[1:]):
parser.add_option('--sources',
dest='sources',
help='path to sources.xml (Firefox OS only)')
parser.add_option('--device',
help='serial identifier of device to target (Firefox OS '
'only)')
(options, args) = parser.parse_args(args)
dm_type = os.environ.get('DM_TRANS', 'adb')
host = os.environ.get('TEST_DEVICE')
version = get_version(binary=options.binary, sources=options.sources,
dm_type=dm_type, host=host)
dm_type=dm_type, host=host,
device_serial=options.device)
for (key, value) in sorted(version.items()):
if value:
print '%s: %s' % (key, value)

View File

@ -558,6 +558,8 @@
try {
// See bug 341047 and comments in overflow handler as to why
// try..catch is needed here
this._updateScrollButtonsDisabledState();
let childNodes = this._getScrollableElements();
if (childNodes && childNodes.length)
this.ensureElementIsVisible(childNodes[0], false);

View File

@ -12,7 +12,7 @@ exports.items = [
{
name: "inject",
description: gcli.lookup("injectDesc"),
manual: gcli.lookup("injectManual"),
manual: gcli.lookup("injectManual2"),
params: [{
name: 'library',
type: {

View File

@ -328,18 +328,29 @@ OutputParser.prototype = {
let colorObj = new colorUtils.CssColor(color);
if (this._isValidColor(colorObj)) {
let container = this._createNode("span", {
"data-color": color
});
if (options.colorSwatchClass) {
this._appendNode("span", {
let swatch = this._createNode("span", {
class: options.colorSwatchClass,
style: "background-color:" + color
});
container.appendChild(swatch);
}
if (options.defaultColorType) {
color = colorObj.toString();
container.dataset["color"] = color;
}
this._appendNode("span", {
let value = this._createNode("span", {
class: options.colorClass
}, color);
container.appendChild(value);
this.parsed.push(container);
return true;
}
return false;
@ -380,7 +391,7 @@ OutputParser.prototype = {
},
/**
* Append a node to the output.
* Create a node.
*
* @param {String} tagName
* Tag type e.g. "div"
@ -389,8 +400,9 @@ OutputParser.prototype = {
* @param {String} [value]
* If a value is included it will be appended as a text node inside
* the tag. This is useful e.g. for span tags.
* @return {Node} Newly created Node.
*/
_appendNode: function(tagName, attributes, value="") {
_createNode: function(tagName, attributes, value="") {
let win = Services.appShell.hiddenDOMWindow;
let doc = win.document;
let node = doc.createElementNS(HTML_NS, tagName);
@ -407,6 +419,22 @@ OutputParser.prototype = {
node.appendChild(textNode);
}
return node;
},
/**
* Append a node to the output.
*
* @param {String} tagName
* Tag type e.g. "div"
* @param {Object} attributes
* e.g. {class: "someClass", style: "cursor:pointer"};
* @param {String} [value]
* If a value is included it will be appended as a text node inside
* the tag. This is useful e.g. for span tags.
*/
_appendNode: function(tagName, attributes, value="") {
let node = this._createNode(tagName, attributes, value);
this.parsed.push(node);
},

View File

@ -28,12 +28,12 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._contentWin = tabActor.window;
this._chromeWin = getChromeWin(tabActor.window);
this._onRefreshDriverTick = this._onRefreshDriverTick.bind(this);
},
destroy: function(conn) {
protocol.Actor.prototype.destroy.call(this, conn);
this.finalize();
this.stopRecording();
},
/**
@ -46,8 +46,8 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
this._recording = true;
this._ticks = [];
this._startTime = this._contentWin.performance.now();
this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
this._startTime = this._chromeWin.performance.now();
this._chromeWin.requestAnimationFrame(this._onRefreshDriverTick);
}, {
}),
@ -61,7 +61,7 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
this._recording = false;
// We don't need to store the ticks array for future use, release it.
let ticks = this._ticks.filter(e => e >= beginAt && e <= endAt);
let ticks = this.getPendingTicks(beginAt, endAt);
this._ticks = null;
return ticks;
}, {
@ -72,6 +72,22 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
response: { ticks: RetVal("array:number") }
}),
/**
* Gets the refresh driver ticks recorded so far.
*/
getPendingTicks: method(function(beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
if (!this._ticks) {
return [];
}
return this._ticks.filter(e => e >= beginAt && e <= endAt);
}, {
request: {
beginAt: Arg(0, "nullable:number"),
endAt: Arg(1, "nullable:number")
},
response: { ticks: RetVal("array:number") }
}),
/**
* Function invoked along with the refresh driver.
*/
@ -79,10 +95,10 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
if (!this._recording) {
return;
}
this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
this._chromeWin.requestAnimationFrame(this._onRefreshDriverTick);
// Store the amount of time passed since the recording started.
let currentTime = this._contentWin.performance.now();
let currentTime = this._chromeWin.performance.now();
let elapsedTime = currentTime - this._startTime;
this._ticks.push(elapsedTime);
}
@ -144,3 +160,19 @@ let FramerateFront = exports.FramerateFront = protocol.FrontClass(FramerateActor
return timeline;
}
});
/**
* Gets the top level browser window from a content window.
*
* @param nsIDOMWindow innerWin
* The content window to query.
* @return nsIDOMWindow
* The top level browser window.
*/
function getChromeWin(innerWin) {
return innerWin
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
}

View File

@ -22,6 +22,7 @@ support-files =
[test_framerate_01.html]
[test_framerate_02.html]
[test_framerate_03.html]
[test_framerate_04.html]
[test_inspector-changeattrs.html]
[test_inspector-changevalue.html]
[test_inspector-hide.html]

View File

@ -0,0 +1,72 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1023018 - Tests if the framerate actor keeps recording after navigations.
-->
<head>
<meta charset="utf-8">
<title>Framerate actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
window.onload = function() {
SimpleTest.waitForExplicitFinish();
var {FramerateFront} = devtools.require("devtools/server/actors/framerate");
var TargetFactory = devtools.TargetFactory;
var url = document.getElementById("testContent").href;
attachURL(url, onTab);
function onTab(_, client, form, contentDoc) {
var contentWin = contentDoc.defaultView;
var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
var selectedTab = chromeWin.gBrowser.selectedTab;
var target = TargetFactory.forTab(selectedTab);
var front = FramerateFront(client, form);
front.startRecording().then(() => {
window.setTimeout(() => {
front.getPendingTicks().then(firstBatch => {
target.once("will-navigate", () => {
window.setTimeout(() => {
front.stopRecording().then(secondBatch => {
onRecordingStopped(client, firstBatch, secondBatch);
});
}, 1000);
});
contentWin.location.reload();
});
}, 1000);
});
}
function onRecordingStopped(client, firstBatch, secondBatch) {
ok(firstBatch, "There should be a first batch recording available.");
ok(secondBatch, "There should be a second batch recording available.");
var diff = secondBatch.length - firstBatch.length;
info("Difference in ticks: " + diff);
ok(diff > 0, "More ticks should be recorded in the second batch.");
ok(firstBatch.every((e) => secondBatch.indexOf(e) != -1),
"All the ticks in the first batch should be in the second batch as well.");
ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true),
"All the ticks in the final batch should be ascending in value.");
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish()
});
}
}
</script>
</pre>
<a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
</body>
</html>

View File

@ -80,6 +80,14 @@ ruleView.contextmenu.copy=Copy
# the rule view context menu "Select all" entry.
ruleView.contextmenu.copy.accessKey=C
# LOCALIZATION NOTE (ruleView.contextmenu.copyColor): Text displayed in the rule
# and computed view context menu when a color value was clicked.
ruleView.contextmenu.copyColor=Copy Color
# LOCALIZATION NOTE (ruleView.contextmenu.copyColor.accessKey): Access key for
# the rule and computed view context menu "Copy Color" entry.
ruleView.contextmenu.copyColor.accessKey=L
# LOCALIZATION NOTE (ruleView.contextmenu.showOrigSources): Text displayed in the rule view
# context menu.
ruleView.contextmenu.showOrigSources=Show original sources

View File

@ -133,7 +133,7 @@ public:
return nullptr;
}
JSContext* const cx = container->mThreadContext;
JS::RootedObject object(cx, container->mJSObject);
JS::RootedObject object(cx, *container->mJSObject);
MOZ_ASSERT(object);
JSAutoStructuredCloneBuffer buffer;
@ -179,9 +179,9 @@ public:
const jint index = env->GetIntField(object, jObjectIndex);
if (index < 0) {
// -1 for index field means it's the root object of the container
return container->mJSObject;
return *container->mJSObject;
}
return container->mRootedObjects[index];
return *container->mRootedObjects[index];
}
static jobject CreateObjectInstance(JNIEnv* env, jobject object,
@ -197,7 +197,8 @@ public:
return nullptr;
}
size_t newIndex = container->mRootedObjects.length();
if (!container->mRootedObjects.append(jsObject)) {
PersistentObjectPtr rootedJSObject(new PersistentObject(cx, jsObject));
if (!container->mRootedObjects.append(Move(rootedJSObject))) {
AndroidBridge::ThrowException(env,
"java/lang/OutOfMemoryError", "Cannot allocate object");
return nullptr;
@ -231,7 +232,7 @@ public:
MOZ_ASSERT(mBuffer.data());
MOZ_ALWAYS_TRUE(mBuffer.read(mThreadContext, &value));
if (value.isObject()) {
mJSObject = &value.toObject();
mJSObject = new PersistentObject(mThreadContext, &value.toObject());
}
if (!mJSObject) {
AndroidBridge::ThrowException(env,
@ -278,22 +279,25 @@ private:
return newObject;
}
typedef JS::PersistentRooted<JSObject*> PersistentObject;
typedef ScopedDeletePtr<PersistentObject> PersistentObjectPtr;
// Thread that the object is valid on
PRThread* mThread;
// Context that the object is valid in
JSContext* mThreadContext;
// Deserialized object, or nullptr if object is in serialized form
JS::Heap<JSObject*> mJSObject;
PersistentObjectPtr mJSObject;
// Serialized object, or empty if object is in deserialized form
JSAutoStructuredCloneBuffer mBuffer;
// Objects derived from mJSObject
Vector<JS::Heap<JSObject*>, 4> mRootedObjects;
Vector<PersistentObjectPtr, 0> mRootedObjects;
// Create a new container containing the given deserialized object
NativeJSContainer(JSContext* cx, JS::HandleObject object)
: mThread(PR_GetCurrentThread())
, mThreadContext(cx)
, mJSObject(object)
, mJSObject(new PersistentObject(cx, object))
{
}
@ -301,6 +305,7 @@ private:
NativeJSContainer(JSContext* cx, JSAutoStructuredCloneBuffer&& buffer)
: mThread(PR_GetCurrentThread())
, mThreadContext(cx)
, mJSObject(nullptr)
, mBuffer(Forward<JSAutoStructuredCloneBuffer>(buffer))
{
}