Merge m-c to b2ginbound, a=merge

This commit is contained in:
Wes Kocher 2015-08-21 10:07:22 -07:00
commit c1622b4ca9
98 changed files with 1291 additions and 332 deletions

View File

@ -213,6 +213,8 @@ input[type=button] {
.newtab-title {
left: 0;
padding: 0 4px;
border: 1px solid #FFFFFF;
border-radius: 0px 0px 8px 8px;
}
.newtab-sponsored {
@ -340,7 +342,7 @@ input[type=button] {
display: -moz-box;
position: relative;
-moz-box-pack: center;
margin: 15px 0px;
margin: 40px 0 15px;
}
#newtab-search-container[page-disabled] {

View File

@ -27,8 +27,6 @@ BROWSER_CHROME_MANIFESTS += [
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
DEFINES['NIGHTLY_BUILD'] = CONFIG['NIGHTLY_BUILD']
DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):

View File

@ -360,26 +360,20 @@ html[dir="rtl"] .contact > .dropdown-menu {
}
.contacts-gravatar-promo {
position: relative;
border: 1px dashed #c1c1c1;
border: 1px solid #5cccee;
border-radius: 2px;
background-color: #fbfbfb;
padding: 10px;
margin-top: 10px;
background-color: #fff;
font-size: 1.2rem;
margin: 1.5rem;
padding: 1.5rem 1rem;
position: relative;
}
.contacts-gravatar-promo > p {
margin-top: 2px;
margin-bottom: 8px;
margin-right: 4px;
margin-top: 0;
word-wrap: break-word;
}
html[dir="rtl"] .contacts-gravatar-promo > p {
margin-right: 0;
margin-left: 4px;
}
.contacts-gravatar-promo > p > a {
color: #0295df;
text-decoration: none;
@ -400,6 +394,44 @@ html[dir="rtl"] .contacts-gravatar-promo > .button-close {
left: 8px;
}
.contacts-gravatar-avatars {
height: 50px;
margin: 1.5rem auto;
text-align: center;
width: 200px;
}
.contacts-gravatar-avatars img {
margin: 0 1.5rem;
vertical-align: middle;
width: 50px;
}
/* Adjust the Firefox avatar because it has pointy ears. */
.contacts-gravatar-avatars img:last-child {
transform: scale(1.08) translateY(-2px);
}
.contacts-gravatar-arrow {
border-color: #9b9b9b;
border-style: solid solid none none;
border-width: 2px;
display: inline-block;
height: 1.5rem;
-moz-margin-start: -.75rem;
transform: rotateZ(45deg);
vertical-align: middle;
width: 1.5rem;
}
html[dir="rtl"] .contacts-gravatar-arrow {
transform: rotateZ(225deg);
}
.contacts-gravatar-buttons {
padding: 0 .5rem;
}
.contact-controls {
padding: 0 16px;
}

View File

@ -265,6 +265,12 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
font-weight: lighter;
}
/* Don't show the empty contacts image if we're showing gravatar promo. */
.contacts-gravatar-promo ~ .contact-list-empty {
background-image: none;
padding-top: 3%;
}
.contact-list-empty {
padding-top: 27%;
}

View File

@ -134,11 +134,17 @@ loop.contacts = (function(_, mozL10n) {
onClick: this.handleCloseButtonClick}),
React.createElement("p", {dangerouslySetInnerHTML: {__html: message},
onClick: this.handleLinkClick}),
React.createElement(ButtonGroup, null,
React.createElement(Button, {caption: mozL10n.get("gravatars_promo_button_nothanks"),
React.createElement("div", {className: "contacts-gravatar-avatars"},
React.createElement("img", {src: "loop/shared/img/avatars.svg#orange-avatar"}),
React.createElement("span", {className: "contacts-gravatar-arrow"}),
React.createElement("img", {src: "loop/shared/img/firefox-avatar.svg"})
),
React.createElement(ButtonGroup, {additionalClass: "contacts-gravatar-buttons"},
React.createElement(Button, {additionalClass: "secondary",
caption: mozL10n.get("gravatars_promo_button_nothanks2"),
onClick: this.handleCloseButtonClick}),
React.createElement(Button, {additionalClass: "button-accept",
caption: mozL10n.get("gravatars_promo_button_use"),
React.createElement(Button, {additionalClass: "secondary",
caption: mozL10n.get("gravatars_promo_button_use2"),
onClick: this.handleUseButtonClick})
)
)
@ -666,10 +672,10 @@ loop.contacts = (function(_, mozL10n) {
return (
React.createElement("div", {className: "contact-list-empty"},
React.createElement("p", {className: "panel-text-large"},
mozL10n.get("no_contacts_message_heading")
mozL10n.get("no_contacts_message_heading2")
),
React.createElement("p", {className: "panel-text-medium"},
mozL10n.get("no_contacts_import_or_add")
mozL10n.get("no_contacts_import_or_add2")
)
)
);
@ -714,7 +720,7 @@ loop.contacts = (function(_, mozL10n) {
busy: this.state.importBusy})})
),
React.createElement(Button, {additionalClass: "primary",
caption: mozL10n.get("new_contact_button"),
caption: mozL10n.get("new_contact_button2"),
onClick: this.handleAddContactButtonClick})
)
);
@ -726,9 +732,7 @@ loop.contacts = (function(_, mozL10n) {
}
return (
React.createElement("div", {className: "content-area"},
React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
)
React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
);
},

View File

@ -134,11 +134,17 @@ loop.contacts = (function(_, mozL10n) {
onClick={this.handleCloseButtonClick} />
<p dangerouslySetInnerHTML={{__html: message}}
onClick={this.handleLinkClick}></p>
<ButtonGroup>
<Button caption={mozL10n.get("gravatars_promo_button_nothanks")}
<div className="contacts-gravatar-avatars">
<img src="loop/shared/img/avatars.svg#orange-avatar" />
<span className="contacts-gravatar-arrow" />
<img src="loop/shared/img/firefox-avatar.svg" />
</div>
<ButtonGroup additionalClass="contacts-gravatar-buttons">
<Button additionalClass="secondary"
caption={mozL10n.get("gravatars_promo_button_nothanks2")}
onClick={this.handleCloseButtonClick}/>
<Button additionalClass="button-accept"
caption={mozL10n.get("gravatars_promo_button_use")}
<Button additionalClass="secondary"
caption={mozL10n.get("gravatars_promo_button_use2")}
onClick={this.handleUseButtonClick}/>
</ButtonGroup>
</div>
@ -666,10 +672,10 @@ loop.contacts = (function(_, mozL10n) {
return (
<div className="contact-list-empty">
<p className="panel-text-large">
{mozL10n.get("no_contacts_message_heading")}
{mozL10n.get("no_contacts_message_heading2")}
</p>
<p className="panel-text-medium">
{mozL10n.get("no_contacts_import_or_add")}
{mozL10n.get("no_contacts_import_or_add2")}
</p>
</div>
);
@ -714,7 +720,7 @@ loop.contacts = (function(_, mozL10n) {
busy: this.state.importBusy})} />
</Button>
<Button additionalClass="primary"
caption={mozL10n.get("new_contact_button")}
caption={mozL10n.get("new_contact_button2")}
onClick={this.handleAddContactButtonClick} />
</ButtonGroup>
);
@ -726,9 +732,7 @@ loop.contacts = (function(_, mozL10n) {
}
return (
<div className="content-area">
<GravatarPromo handleUse={this.handleUseGravatar}/>
</div>
<GravatarPromo handleUse={this.handleUseGravatar} />
);
},

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -95,6 +95,7 @@ browser.jar:
content/browser/loop/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
content/browser/loop/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
content/browser/loop/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
# Shared scripts
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)

View File

@ -203,6 +203,9 @@ describe("loop.contacts", function() {
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.not.equal(null);
var avatars = listView.getDOMNode().querySelectorAll(".contacts-gravatar-avatars img");
expect(avatars).to.have.length(2, "two example avatars are shown");
checkGravatarContacts(false);
});
@ -238,7 +241,7 @@ describe("loop.contacts", function() {
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-accept"));
".contacts-gravatar-promo .secondary:last-child"));
sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
@ -255,7 +258,7 @@ describe("loop.contacts", function() {
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-accept"));
".contacts-gravatar-promo .secondary:last-child"));
sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.promo", false);
@ -271,7 +274,7 @@ describe("loop.contacts", function() {
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-close"));
".contacts-gravatar-promo .secondary:first-child"));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
@ -285,6 +288,37 @@ describe("loop.contacts", function() {
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .secondary:first-child"));
sinon.assert.calledOnce(navigator.mozLoop.setLoopPref);
sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref,
"contacts.gravatars.promo", false);
});
it("should hide the gravatars promo box when the 'close' X button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-close"));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
});
it("should set prefs correctly when the 'close' X button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-close"));
@ -326,9 +360,9 @@ describe("loop.contacts", function() {
it("should display the no contacts strings", function() {
sinon.assert.calledWithExactly(mozL10nGetSpy,
"no_contacts_message_heading");
"no_contacts_message_heading2");
sinon.assert.calledWithExactly(mozL10nGetSpy,
"no_contacts_import_or_add");
"no_contacts_import_or_add2");
});
});

View File

@ -87,6 +87,8 @@ support-files =
doc_pretty-print-3.html
doc_pretty-print-on-paused.html
doc_promise-get-allocation-stack.html
doc_promise-get-fulfillment-stack.html
doc_promise-get-rejection-stack.html
doc_promise.html
doc_random-javascript.html
doc_recursion-stack.html
@ -349,6 +351,10 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_promises-chrome-allocation-stack.js]
skip-if = true # Bug 1177730
[browser_dbg_promises-fulfillment-stack.js]
skip-if = e10s && debug
[browser_dbg_promises-rejection-stack.js]
skip-if = e10s && debug
[browser_dbg_reload-preferred-script-01.js]
skip-if = e10s && debug
[browser_dbg_reload-preferred-script-02.js]

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can get a stack to a promise's fulfillment point.
*/
"use strict";
const TAB_URL = EXAMPLE_URL + "doc_promise-get-fulfillment-stack.html";
const { PromisesFront } = require("devtools/server/actors/promises");
let events = require("sdk/event/core");
const TEST_DATA = [
{
functionDisplayName: "returnPromise/<",
line: 19,
column: 37
},
{
functionDisplayName: "returnPromise",
line: 19,
column: 14
},
{
functionDisplayName: "makePromise",
line: 14,
column: 15
},
];
function test() {
Task.spawn(function* () {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
const [ tab,, panel ] = yield initDebugger(TAB_URL);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let { tabs } = yield listTabs(client);
let targetTab = findTab(tabs, TAB_URL);
yield attachTab(client, targetTab);
yield testGetFulfillmentStack(client, targetTab, tab);
yield close(client);
yield closeDebuggerAndFinish(panel);
}).then(null, error => {
ok(false, "Got an error: " + error.message + "\n" + error.stack);
});
}
function* testGetFulfillmentStack(client, form, tab) {
let front = PromisesFront(client, form);
yield front.attach();
yield front.listPromises();
// Get the grip for promise p
let onNewPromise = new Promise(resolve => {
events.on(front, "new-promises", promises => {
for (let p of promises) {
if (p.preview.ownProperties.name &&
p.preview.ownProperties.name.value === "p") {
resolve(p);
}
}
});
});
callInTab(tab, "makePromise");
let grip = yield onNewPromise;
ok(grip, "Found our promise p");
let objectClient = new ObjectClient(client, grip);
ok(objectClient, "Got Object Client");
yield new Promise(resolve => {
objectClient.getPromiseFulfillmentStack(response => {
ok(response.fulfillmentStack.length, "Got promise allocation stack.");
for (let i = 0; i < TEST_DATA.length; i++) {
let stack = response.fulfillmentStack[i];
let data = TEST_DATA[i];
is(stack.source.url, TAB_URL, "Got correct source URL.");
is(stack.functionDisplayName, data.functionDisplayName,
"Got correct function display name.");
is(stack.line, data.line, "Got correct stack line number.");
is(stack.column, data.column, "Got correct stack column number.");
}
resolve();
});
});
yield front.detach();
}

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can get a stack to a promise's rejection point.
*/
"use strict";
const TAB_URL = EXAMPLE_URL + "doc_promise-get-rejection-stack.html";
const { PromisesFront } = require("devtools/server/actors/promises");
let events = require("sdk/event/core");
const TEST_DATA = [
{
functionDisplayName: "returnPromise/<",
line: 19,
column: 47
},
{
functionDisplayName: "returnPromise",
line: 19,
column: 14
},
{
functionDisplayName: "makePromise",
line: 14,
column: 15
},
];
function test() {
Task.spawn(function* () {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
const [ tab,, panel ] = yield initDebugger(TAB_URL);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let { tabs } = yield listTabs(client);
let targetTab = findTab(tabs, TAB_URL);
yield attachTab(client, targetTab);
yield testGetRejectionStack(client, targetTab, tab);
yield close(client);
yield closeDebuggerAndFinish(panel);
}).then(null, error => {
ok(false, "Got an error: " + error.message + "\n" + error.stack);
});
}
function* testGetRejectionStack(client, form, tab) {
let front = PromisesFront(client, form);
yield front.attach();
yield front.listPromises();
// Get the grip for promise p
let onNewPromise = new Promise(resolve => {
events.on(front, "new-promises", promises => {
for (let p of promises) {
if (p.preview.ownProperties.name &&
p.preview.ownProperties.name.value === "p") {
resolve(p);
}
}
});
});
callInTab(tab, "makePromise");
let grip = yield onNewPromise;
ok(grip, "Found our promise p");
let objectClient = new ObjectClient(client, grip);
ok(objectClient, "Got Object Client");
yield new Promise(resolve => {
objectClient.getPromiseRejectionStack(response => {
ok(response.rejectionStack.length, "Got promise allocation stack.");
for (let i = 0; i < TEST_DATA.length; i++) {
let stack = response.rejectionStack[i];
let data = TEST_DATA[i];
is(stack.source.url, TAB_URL, "Got correct source URL.");
is(stack.functionDisplayName, data.functionDisplayName,
"Got correct function display name.");
is(stack.line, data.line, "Got correct stack line number.");
is(stack.column, data.column, "Got correct stack column number.");
}
resolve();
});
});
yield front.detach();
}

View File

@ -0,0 +1,24 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Promise test page</title>
</head>
<body>
<script type="text/javascript">
function makePromise() {
var p = returnPromise();
p.name = "p";
}
function returnPromise() {
return new Promise(resolve => resolve("hello"));
}
</script>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Promise test page</title>
</head>
<body>
<script type="text/javascript">
function makePromise() {
var p = returnPromise();
p.name = "p";
}
function returnPromise() {
return new Promise((resolve, reject) => reject("hello"));
}
</script>
</body>
</html>

View File

@ -2,18 +2,22 @@
* 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/. */
let { interfaces: Ci } = Components;
addMessageListener("Eyedropper:RequestContentScreenshot", sendContentScreenshot);
function sendContentScreenshot() {
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let scale = content.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
let width = content.innerWidth;
let height = content.innerHeight;
canvas.width = width;
canvas.height = height;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
sendAsyncMessage("Eyedropper:Screenshot", canvas.toDataURL());

View File

@ -362,7 +362,7 @@ const Formatters = {
GCFields: function (marker) {
let fields = Object.create(null);
let cause = marker.cause;
let cause = marker.causeName;
let label = L10N.getStr(`marker.gcreason.label.${cause}`) || cause;
fields[L10N.getStr("marker.field.causeName")] = label;

View File

@ -90,7 +90,7 @@ const EVENTS = {
UI_STOP_RECORDING: "Performance:UI:StopRecording",
// Emitted by the PerformanceView on import button click
UI_RECORDING_IMPORTED: "Performance:UI:ImportRecording",
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
// Emitted by the RecordingsView on export button click
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
@ -226,7 +226,7 @@ let PerformanceController = {
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
PerformanceView.on(EVENTS.UI_RECORDING_IMPORTED, this.importRecording);
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
@ -244,7 +244,7 @@ let PerformanceController = {
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
PerformanceView.off(EVENTS.UI_RECORDING_IMPORTED, this.importRecording);
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);

View File

@ -7,7 +7,7 @@
let test = Task.async(function*() {
var { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
var { EVENTS, PerformanceController, DetailsView, DetailsSubview } = panel.panelWin;
var { EVENTS, PerformanceController, PerformanceView, DetailsView, DetailsSubview } = panel.panelWin;
// Enable allocations to test the memory-calltree and memory-flamegraph.
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
@ -49,7 +49,7 @@ let test = Task.async(function*() {
let rerendered = waitForWidgetsRendered(panel);
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
PerformanceView.emit(EVENTS.UI_IMPORT_RECORDING, file);
yield imported;
ok(true, "The recording data appears to have been successfully imported.");

View File

@ -39,10 +39,10 @@ add_task(function () {
equal(fields[1].label, "Phase:", "getMarkerFields() correctly returns fields via function (3)");
equal(fields[1].value, "Target", "getMarkerFields() correctly returns fields via function (4)");
fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "ALLOC_TRIGGER" });
fields = Utils.getMarkerFields({ name: "GarbageCollection", causeName: "ALLOC_TRIGGER" });
equal(fields[0].value, "Too Many Allocations", "Uses L10N for GC reasons");
fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "NOT_A_GC_REASON" });
fields = Utils.getMarkerFields({ name: "GarbageCollection", causeName: "NOT_A_GC_REASON" });
equal(fields[0].value, "NOT_A_GC_REASON", "Defaults to enum for GC reasons when not L10N'd");
equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)",

View File

@ -88,9 +88,9 @@ settings_menu_button_tooltip=Settings
## the search field.
contacts_search_placesholder2=Search…
## LOCALIZATION NOTE (new_contact_button): This is the button to open the
## LOCALIZATION NOTE (new_contact_button2): This is the button to open the
## new contact sub-panel.
new_contact_button=New Contact
new_contact_button2=Add new contact
## LOCALIZATION NOTE (contact_form_*_placeholder):
## These are the placeholders for the inputs for entering or editing a contact
## Click the 'New Contact' button to see the fields.
@ -131,12 +131,12 @@ import_contacts_success_message={{total}} contact was successfully imported.;{{t
## LOCALIZATION NOTE(sync_contacts_button): This button is displayed in place of
## importing_contacts_button once contacts have been imported once.
sync_contacts_button=Sync Contacts
## LOCALIZATION NOTE(no_contacts_message_heading): Title shown when user has no
## LOCALIZATION NOTE(no_contacts_message_heading2): Title shown when user has no
## contacts in his address book
no_contacts_message_heading=No contacts yet
## LOCALIZATION NOTE(no_contacts_import_or_add): Subheading inviting the user
no_contacts_message_heading2=No contacts yet.
## LOCALIZATION NOTE(no_contacts_import_or_add2): Subheading inviting the user
## to add people to his contact list
no_contacts_import_or_add=Import or add someone
no_contacts_import_or_add2=Add someone!
## LOCALIZATION NOTE(no_conversations_message_heading): Title shown when user
## has no conversations available.
no_conversations_message_heading=There are no conversations yet
@ -208,8 +208,8 @@ video_call_menu_button=Video Conversation
gravatars_promo_message=You can automatically add profile icons to your contacts \
by sharing their email addresses with Gravatar. {{learn_more}}.
gravatars_promo_message_learnmore=Learn more
gravatars_promo_button_nothanks=No Thanks
gravatars_promo_button_use=Use Profile Icons
gravatars_promo_button_nothanks2=No, thanks
gravatars_promo_button_use2=Use profile icons
# Conversation Window Strings

View File

@ -167,7 +167,10 @@
}
.newtab-site:hover .newtab-title {
color: #222;
color: white;
background-color: black;
border: 1px solid black;
border-top: 1px solid white;
}
.newtab-site[pinned] .newtab-title {

View File

@ -76,7 +76,7 @@ struct EventRadiusPrefs
bool mRegistered;
bool mTouchOnly;
bool mRepositionEventCoords;
bool mTouchClusterDetectionDisabled;
bool mTouchClusterDetectionEnabled;
uint32_t mLimitReadableSize;
};
@ -125,8 +125,8 @@ GetPrefsFor(EventClassID aEventClassID)
nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
nsPrintfCString touchClusterPref("ui.zoomedview.disabled", prefBranch);
Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionDisabled, touchClusterPref.get(), true);
nsPrintfCString touchClusterPref("ui.zoomedview.enabled", prefBranch);
Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionEnabled, touchClusterPref.get(), false);
nsPrintfCString limitReadableSizePref("ui.zoomedview.limitReadableSize", prefBranch);
Preferences::AddUintVarCache(&prefs->mLimitReadableSize, limitReadableSizePref.get(), 8);
@ -451,7 +451,7 @@ GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
static bool
IsElementClickableAndReadable(nsIFrame* aFrame, WidgetGUIEvent* aEvent, const EventRadiusPrefs* aPrefs)
{
if (aPrefs->mTouchClusterDetectionDisabled) {
if (!aPrefs->mTouchClusterDetectionEnabled) {
return true;
}
@ -578,7 +578,7 @@ FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
restrictToDescendants, clickableAncestor, candidates,
&elementsInCluster);
if (closestClickable) {
if ((!prefs->mTouchClusterDetectionDisabled && elementsInCluster > 1) ||
if ((prefs->mTouchClusterDetectionEnabled && elementsInCluster > 1) ||
(!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {
if (aEvent->mClass == eMouseEventClass) {
WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();

View File

@ -414,7 +414,7 @@ pref("font.size.inflation.minTwips", 0);
// When true, zooming will be enabled on all sites, even ones that declare user-scalable=no.
pref("browser.ui.zoom.force-user-scalable", false);
pref("ui.zoomedview.disabled", false);
pref("ui.zoomedview.enabled", true);
pref("ui.zoomedview.limitReadableSize", 8); // value in layer pixels
pref("ui.zoomedview.defaultZoomFactor", 2);
pref("ui.zoomedview.simplified", true); // Do not display all the zoomed view controls

View File

@ -777,7 +777,7 @@ public class BrowserApp extends GeckoApp
}
if (HardwareUtils.isTablet()) {
mTabStrip = (Refreshable) (((ViewStub) findViewById(R.id.new_tablet_tab_strip)).inflate());
mTabStrip = (Refreshable) (((ViewStub) findViewById(R.id.tablet_tab_strip)).inflate());
}
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());

View File

@ -1962,6 +1962,8 @@ public abstract class GeckoApp
}
}
});
RestrictedProfiles.update(this);
}
@Override

View File

@ -311,7 +311,7 @@ res/raw/browsersearch.json: .locales.deps ;
res/raw/suggestedsites.json: .locales.deps ;
all_resources = \
$(CURDIR)/AndroidManifest.xml \
$(abspath $(CURDIR)/AndroidManifest.xml) \
$(android_res_files) \
$(ANDROID_GENERATED_RESFILES) \
$(NULL)
@ -420,8 +420,8 @@ endef
# .aapt.deps: $(all_resources)
$(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/))
# .aapt.nodeps: $(abspath $(CURDIR)/AndroidManifest.xml) FORCE
$(eval $(call aapt_command,.aapt.nodeps,$(abspath $(CURDIR)/AndroidManifest.xml) FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
# Override the Java settings with some specific android settings
include $(topsrcdir)/config/android-common.mk
@ -446,6 +446,7 @@ $(abspath $(DIST)/fennec/$(OMNIJAR_NAME)): FORCE
$(MAKE) -C ../app
$(MAKE) -C ../themes/core
$(MAKE) -C ../installer stage-package
$(MKDIR) -p $(@D)
rsync --update $(DIST)/fennec/$(notdir $(OMNIJAR_NAME)) $@
$(RM) $(DIST)/fennec/$(notdir $(OMNIJAR_NAME))

View File

@ -43,7 +43,7 @@ public class RestrictedProfiles {
if (isGuestProfile(context)) {
return new GuestProfileConfiguration();
} else if(isRestrictedProfile(context)) {
} else if (isRestrictedProfile(context)) {
return new RestrictedProfileConfiguration(context);
} else {
return new DefaultConfiguration();
@ -51,6 +51,10 @@ public class RestrictedProfiles {
}
private static boolean isGuestProfile(Context context) {
if (configuration != null) {
return configuration instanceof GuestProfileConfiguration;
}
GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface();
if (geckoInterface != null) {
return geckoInterface.getProfile().inGuestMode();
@ -61,6 +65,10 @@ public class RestrictedProfiles {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public static boolean isRestrictedProfile(Context context) {
if (configuration != null) {
return configuration instanceof RestrictedProfileConfiguration;
}
if (Versions.preJBMR2) {
// Early versions don't support restrictions at all
return false;
@ -81,6 +89,10 @@ public class RestrictedProfiles {
return false;
}
public static void update(Context context) {
getConfiguration(context).update();
}
private static Restriction geckoActionToRestriction(int action) {
for (Restriction rest : Restriction.values()) {
if (rest.id == action) {

View File

@ -415,8 +415,8 @@ size. -->
<!-- Localization note (pref_prevent_magnifying_glass): Label for setting that controls
whether or not the magnifying glass is disabled. -->
<!ENTITY pref_prevent_magnifying_glass "Disable magnifying glass ">
<!ENTITY pref_prevent_magnifying_glass_summary "Prevent magnifying glass for use in resolving ambiguous screen touches">
<!ENTITY pref_magnifying_glass_enabled "Magnify small areas">
<!ENTITY pref_magnifying_glass_enabled_summary "Enlarge links and form fields when tapping near them">
<!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
whether or not the dynamic toolbar is enabled. -->

View File

@ -117,7 +117,7 @@ OnSharedPreferenceChangeListener
private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled";
private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding";
private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled";
private static final String PREFS_DISABLE_ZOOMED_VIEW = "ui.zoomedview.disabled";
private static final String PREFS_ZOOMED_VIEW_ENABLED = "ui.zoomedview.enabled";
private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload";
private static final String PREFS_UPDATER_URL = "app.update.url.android";
private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata";
@ -735,7 +735,7 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_DISABLE_ZOOMED_VIEW.equals(key)) {
} else if (PREFS_ZOOMED_VIEW_ENABLED.equals(key)) {
// Only enable the ZoomedView / magnifying pref on Nightly.
if (!AppConstants.NIGHTLY_BUILD) {
preferences.removePreference(pref);

View File

@ -32,10 +32,10 @@
android:color="@color/background_normal_lwt" />
<item android:state_pressed="true"
gecko:state_light="true"
android:color="@color/new_tablet_highlight_lwt" />
android:color="@color/tablet_highlight_lwt" />
<item android:state_pressed="true"
gecko:state_dark="true"
android:color="@color/new_tablet_highlight_dark_lwt" />
android:color="@color/tablet_highlight_dark_lwt" />
<item android:state_checked="true"
android:color="@color/toolbar_grey" />

View File

@ -10,13 +10,13 @@
android:state_pressed="true"
android:state_enabled="true">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/text_and_tabs_tray_grey"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
@ -26,13 +26,13 @@
android:state_focused="true"
android:state_pressed="false">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/placeholder_active_grey"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
@ -41,13 +41,13 @@
<item android:state_pressed="true"
android:state_enabled="true">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/toolbar_grey_pressed"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
@ -56,13 +56,13 @@
<item android:state_focused="true"
android:state_pressed="false">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/new_tablet_highlight_focused"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<solid android:color="@color/tablet_highlight_focused"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>

View File

@ -9,13 +9,13 @@
<item android:state_pressed="true"
android:state_enabled="true">
<inset android:insetTop="@dimen/new_tablet_tab_strip_button_inset"
android:insetBottom="@dimen/new_tablet_tab_strip_button_inset"
android:insetLeft="@dimen/new_tablet_tab_strip_button_inset"
android:insetRight="@dimen/new_tablet_tab_strip_button_inset">
<inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
android:insetBottom="@dimen/tablet_tab_strip_button_inset"
android:insetLeft="@dimen/tablet_tab_strip_button_inset"
android:insetRight="@dimen/tablet_tab_strip_button_inset">
<shape android:shape="rectangle">
<solid android:color="@color/highlight_dark"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
@ -24,13 +24,13 @@
<item android:state_focused="true"
android:state_pressed="false">
<inset android:insetTop="@dimen/new_tablet_tab_strip_button_inset"
android:insetBottom="@dimen/new_tablet_tab_strip_button_inset"
android:insetLeft="@dimen/new_tablet_tab_strip_button_inset"
android:insetRight="@dimen/new_tablet_tab_strip_button_inset">
<inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
android:insetBottom="@dimen/tablet_tab_strip_button_inset"
android:insetLeft="@dimen/tablet_tab_strip_button_inset"
android:insetRight="@dimen/tablet_tab_strip_button_inset">
<shape android:shape="rectangle">
<solid android:color="@color/new_tablet_highlight_focused"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
<solid android:color="@color/tablet_highlight_focused"/>
<corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>

View File

@ -24,7 +24,7 @@
<!-- focused state -->
<item android:state_focused="true"
android:state_pressed="false"
android:drawable="@color/new_tablet_highlight_focused"/>
android:drawable="@color/tablet_highlight_focused"/>
<!-- private browsing mode -->
<item gecko:state_private="true"

View File

@ -11,7 +11,7 @@
android:layout_height="match_parent"
android:layout_alignLeft="@+id/back"
android:layout_toLeftOf="@id/menu_items"
android:layout_marginLeft="@dimen/new_tablet_nav_button_width_half"
android:layout_marginLeft="@dimen/tablet_nav_button_width_half"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:duplicateParentState="true"
@ -51,14 +51,14 @@
android:src="@drawable/ic_menu_forward"
android:background="@drawable/url_bar_nav_button"
android:alpha="0"
android:layout_width="@dimen/new_tablet_nav_button_width_plus_half"
android:layout_marginLeft="@dimen/new_tablet_nav_button_width_half"
android:layout_width="@dimen/tablet_nav_button_width_plus_half"
android:layout_marginLeft="@dimen/tablet_nav_button_width_half"
android:paddingLeft="18dp"/>
<org.mozilla.gecko.toolbar.BackButton android:id="@id/back"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/new_tablet_nav_button_width"
android:layout_height="@dimen/new_tablet_nav_button_width"
android:layout_width="@dimen/tablet_nav_button_width"
android:layout_height="@dimen/tablet_nav_button_width"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_alignParentLeft="true"

View File

@ -17,7 +17,7 @@
<org.mozilla.gecko.widget.ThemedImageButton
android:id="@+id/add_tab"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/new_tablet_tab_strip_height"
android:layout_width="@dimen/tablet_tab_strip_height"
android:src="@drawable/tab_strip_add_tab"
android:contentDescription="@string/new_tab"
android:layout_marginRight="9dp"

View File

@ -7,7 +7,7 @@
the close button within the TabStripItemView -->
<org.mozilla.gecko.tabs.TabStripItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/new_tablet_tab_strip_item_width"
android:layout_width="@dimen/tablet_tab_strip_item_width"
android:layout_height="match_parent"
android:paddingLeft="28dp"
android:paddingRight="12dp"/>

View File

@ -12,6 +12,6 @@
android:src="@drawable/tabs_panel_nav_back"
android:contentDescription="@string/back"
android:background="@drawable/action_bar_button_inverse"
gecko:dividerVerticalPadding="@dimen/new_tablet_tab_panel_divider_vertical_padding"
gecko:dividerVerticalPadding="@dimen/tablet_tab_panel_divider_vertical_padding"
gecko:rightDivider="@drawable/tab_indicator_divider"/>

View File

@ -16,9 +16,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:duplicateParentState="true"
android:paddingLeft="@dimen/new_tablet_tab_highlight_stroke_width"
android:paddingRight="@dimen/new_tablet_tab_highlight_stroke_width"
android:paddingBottom="@dimen/new_tablet_tab_highlight_stroke_width">
android:paddingLeft="@dimen/tablet_tab_highlight_stroke_width"
android:paddingRight="@dimen/tablet_tab_highlight_stroke_width"
android:paddingBottom="@dimen/tablet_tab_highlight_stroke_width">
<org.mozilla.gecko.widget.FadedSingleColorTextView
android:id="@+id/title"
@ -62,13 +62,13 @@
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
android:padding="@dimen/tablet_tab_highlight_stroke_width"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
android:layout_height="@dimen/new_tablet_tab_thumbnail_height"
android:layout_width="@dimen/tablet_tab_thumbnail_width"
android:layout_height="@dimen/tablet_tab_thumbnail_height"
/>
</org.mozilla.gecko.widget.TabThumbnailWrapper>

View File

@ -23,7 +23,7 @@
<RelativeLayout android:id="@+id/gecko_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/new_tablet_tab_strip"
android:layout_below="@+id/tablet_tab_strip"
android:layout_above="@+id/find_in_page">
<include layout="@layout/shared_ui_components"/>
@ -98,11 +98,11 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ViewStub android:id="@+id/new_tablet_tab_strip"
android:inflatedId="@id/new_tablet_tab_strip"
<ViewStub android:id="@+id/tablet_tab_strip"
android:inflatedId="@id/tablet_tab_strip"
android:layout="@layout/tab_strip"
android:layout_width="match_parent"
android:layout_height="@dimen/new_tablet_tab_strip_height"
android:layout_height="@dimen/tablet_tab_strip_height"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoViewFlipper

View File

@ -28,7 +28,7 @@
android:layout_height="match_parent"
android:tabStripEnabled="false"
android:divider="@drawable/tab_indicator_divider"
android:dividerPadding="@dimen/new_tablet_tab_panel_divider_vertical_padding"
android:dividerPadding="@dimen/tablet_tab_panel_divider_vertical_padding"
android:layout="@layout/tabs_panel_indicator"/>
<View android:layout_width="0dip"

View File

@ -9,5 +9,5 @@
<dimen name="home_remote_tabs_top_padding">16dp</dimen>
<dimen name="page_group_height">64dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding">48dp</dimen>
<dimen name="tablet_tab_panel_grid_padding">48dp</dimen>
</resources>

View File

@ -6,7 +6,7 @@
<resources>
<style name="UrlBar.ImageButton" parent="UrlBar.ImageButtonBase">
<item name="android:layout_width">@dimen/new_tablet_browser_toolbar_menu_item_width</item>
<item name="android:layout_width">@dimen/tablet_browser_toolbar_menu_item_width</item>
</style>
<style name="UrlBar.ImageButton.TabCount">
@ -54,8 +54,8 @@
* The reload button contains whitespace at the top of the image to lower it -->
<item name="android:paddingTop">19dp</item>
<item name="android:paddingBottom">21dp</item>
<item name="android:paddingLeft">@dimen/new_tablet_browser_toolbar_menu_item_padding_horizontal</item>
<item name="android:paddingRight">@dimen/new_tablet_browser_toolbar_menu_item_padding_horizontal</item>
<item name="android:paddingLeft">@dimen/tablet_browser_toolbar_menu_item_padding_horizontal</item>
<item name="android:paddingRight">@dimen/tablet_browser_toolbar_menu_item_padding_horizontal</item>
</style>
<style name="Widget.MenuItemSecondaryActionBar" parent="Widget.MenuItemSecondaryActionBarBase">

View File

@ -5,6 +5,6 @@
<resources>
<dimen name="new_tablet_tab_panel_grid_padding">64dp</dimen>
<dimen name="tablet_tab_panel_grid_padding">64dp</dimen>
</resources>

View File

@ -6,6 +6,6 @@
<resources>
<dimen name="panel_grid_view_column_width">250dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding">48dp</dimen>
<dimen name="tablet_tab_panel_grid_padding">48dp</dimen>
</resources>

View File

@ -47,13 +47,13 @@
<color name="highlight_dark_focused">#1AFFFFFF</color>
<!-- Synced w/ toolbar_grey_pressed -->
<color name="new_tablet_highlight_lwt">#AAD7D7DC</color>
<color name="tablet_highlight_lwt">#AAD7D7DC</color>
<!-- Synced w/ tabs_tray_grey_pressed -->
<color name="new_tablet_highlight_dark_lwt">#AA45494E</color>
<color name="tablet_highlight_dark_lwt">#AA45494E</color>
<!-- (bug 1077195) Focused state values are temporary. -->
<color name="new_tablet_highlight_focused">#C0C9D0</color>
<color name="tablet_highlight_focused">#C0C9D0</color>
<!-- highlight on shaped button: 20% white over text_and_tabs_tray_grey -->
<color name="highlight_shaped">#FF696D71</color>

View File

@ -28,25 +28,25 @@
<dimen name="browser_toolbar_shadow_size">2dp</dimen>
<!-- If you update one of these values, update the others. -->
<dimen name="new_tablet_nav_button_width">42dp</dimen>
<dimen name="new_tablet_nav_button_width_half">21dp</dimen>
<dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
<dimen name="tablet_nav_button_width">42dp</dimen>
<dimen name="tablet_nav_button_width_half">21dp</dimen>
<dimen name="tablet_nav_button_width_plus_half">63dp</dimen>
<!-- This is the system default for the vertical padding for the divider of the TabWidget.
Used to mimic the divider padding on the tablet tabs panel back button. -->
<dimen name="new_tablet_tab_panel_divider_vertical_padding">12dp</dimen>
<dimen name="tablet_tab_panel_divider_vertical_padding">12dp</dimen>
<dimen name="new_tablet_tab_strip_height">48dp</dimen>
<dimen name="new_tablet_tab_strip_item_width">208dp</dimen>
<dimen name="new_tablet_tab_strip_item_margin">-28dp</dimen>
<dimen name="new_tablet_tab_strip_fading_edge_size">15dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_width">56dp</dimen>
<dimen name="tablet_tab_strip_height">48dp</dimen>
<dimen name="tablet_tab_strip_item_width">208dp</dimen>
<dimen name="tablet_tab_strip_item_margin">-28dp</dimen>
<dimen name="tablet_tab_strip_fading_edge_size">15dp</dimen>
<dimen name="tablet_browser_toolbar_menu_item_width">56dp</dimen>
<!-- Padding combines with an 18dp image to form the menu item width and height. -->
<dimen name="new_tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_corner_radius">5dp</dimen>
<dimen name="new_tablet_tab_strip_button_inset">5dp</dimen>
<dimen name="tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
<dimen name="tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
<dimen name="tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
<dimen name="tablet_browser_toolbar_menu_item_corner_radius">5dp</dimen>
<dimen name="tablet_tab_strip_button_inset">5dp</dimen>
<!-- Dimensions used by Favicons and FaviconView -->
<dimen name="favicon_bg">32dp</dimen>
@ -145,14 +145,14 @@
<dimen name="validation_message_height">50dp</dimen>
<dimen name="validation_message_margin_top">6dp</dimen>
<dimen name="new_tablet_tab_thumbnail_width">168dp</dimen>
<dimen name="new_tablet_tab_thumbnail_height">140dp</dimen>
<dimen name="new_tablet_tab_panel_column_width">178dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding">19dp</dimen>
<dimen name="new_tablet_tab_panel_grid_vspacing">21dp</dimen>
<dimen name="new_tablet_tab_panel_grid_padding_top">24dp</dimen>
<dimen name="tablet_tab_thumbnail_width">168dp</dimen>
<dimen name="tablet_tab_thumbnail_height">140dp</dimen>
<dimen name="tablet_tab_panel_column_width">178dp</dimen>
<dimen name="tablet_tab_panel_grid_padding">19dp</dimen>
<dimen name="tablet_tab_panel_grid_vspacing">21dp</dimen>
<dimen name="tablet_tab_panel_grid_padding_top">24dp</dimen>
<dimen name="new_tablet_tab_highlight_stroke_width">5dp</dimen>
<dimen name="tablet_tab_highlight_stroke_width">5dp</dimen>
<!-- PageActionButtons dimensions -->
<dimen name="page_action_button_width">32dp</dimen>

View File

@ -208,9 +208,9 @@
<item name="android:scrollbarStyle">outsideOverlay</item>
<item name="android:gravity">center</item>
<item name="android:numColumns">auto_fit</item>
<item name="android:columnWidth">@dimen/new_tablet_tab_panel_column_width</item>
<item name="android:columnWidth">@dimen/tablet_tab_panel_column_width</item>
<item name="android:horizontalSpacing">2dp</item>
<item name="android:verticalSpacing">@dimen/new_tablet_tab_panel_grid_vspacing</item>
<item name="android:verticalSpacing">@dimen/tablet_tab_panel_grid_vspacing</item>
<item name="android:drawSelectorOnTop">true</item>
<item name="android:clipToPadding">false</item>
</style>
@ -688,8 +688,8 @@
<item name="android:layout_weight">1</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:ellipsize">none</item>
<item name="android:maxLines">3</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item>
<item name="android:clickable">false</item>
<item name="android:focusable">false</item>
</style>

View File

@ -33,9 +33,9 @@
android:title="@string/pref_zoom_force_enabled"
android:summary="@string/pref_zoom_force_enabled_summary" />
<CheckBoxPreference android:key="ui.zoomedview.disabled"
android:title="@string/pref_prevent_magnifying_glass"
android:summary="@string/pref_prevent_magnifying_glass_summary" />
<CheckBoxPreference android:key="ui.zoomedview.enabled"
android:title="@string/pref_magnifying_glass_enabled"
android:summary="@string/pref_magnifying_glass_enabled_summary" />
<PreferenceCategory android:title="@string/pref_category_input_options"
android:key="@string/pref_category_input_options">

View File

@ -24,4 +24,7 @@ public class DefaultConfiguration implements RestrictionConfiguration {
public boolean isRestricted() {
return false;
}
@Override
public void update() {}
}

View File

@ -72,4 +72,7 @@ public class GuestProfileConfiguration implements RestrictionConfiguration {
public boolean isRestricted() {
return true;
}
@Override
public void update() {}
}

View File

@ -6,16 +6,19 @@
package org.mozilla.gecko.restrictions;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.util.ThreadUtils;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.os.UserManager;
import java.util.Arrays;
import java.util.List;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class RestrictedProfileConfiguration implements RestrictionConfiguration {
static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_INSTALL_EXTENSION,
@ -31,22 +34,36 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
);
private Context context;
private Bundle cachedRestrictions;
private boolean isCacheInvalid = true;
public RestrictedProfileConfiguration(Context context) {
this.context = context.getApplicationContext();
}
@Override
public boolean isAllowed(Restriction restriction) {
boolean isAllowed = !getAppRestrictions(context).getBoolean(restriction.name, DEFAULT_RESTRICTIONS.contains(restriction));
if (isAllowed) {
// If this restriction is not enforced by the app setup then check wether this is a restriction that is
// enforced by the system.
isAllowed = !getUserRestrictions(context).getBoolean(restriction.name, false);
public synchronized boolean isAllowed(Restriction restriction) {
if (isCacheInvalid || !ThreadUtils.isOnUiThread()) {
cachedRestrictions = readRestrictions();
isCacheInvalid = false;
}
return isAllowed;
return !cachedRestrictions.getBoolean(restriction.name, DEFAULT_RESTRICTIONS.contains(restriction));
}
private Bundle readRestrictions() {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
try {
Bundle restrictions = new Bundle();
restrictions.putAll(mgr.getApplicationRestrictions(context.getPackageName()));
restrictions.putAll(mgr.getUserRestrictions());
return restrictions;
} finally {
StrictMode.setThreadPolicy(policy);
}
}
@Override
@ -72,15 +89,8 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
return true;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private Bundle getAppRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getApplicationRestrictions(context.getPackageName());
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private Bundle getUserRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getUserRestrictions();
@Override
public synchronized void update() {
isCacheInvalid = true;
}
}

View File

@ -23,4 +23,9 @@ public interface RestrictionConfiguration {
* Is this user restricted in any way?
*/
boolean isRestricted();
/**
* Update restrictions if needed.
*/
void update();
}

View File

@ -379,8 +379,8 @@
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
<string name="pref_titlebar_mode_url">&pref_titlebar_mode_url;</string>
<string name="pref_prevent_magnifying_glass">&pref_prevent_magnifying_glass;</string>
<string name="pref_prevent_magnifying_glass_summary">&pref_prevent_magnifying_glass_summary;</string>
<string name="pref_magnifying_glass_enabled">&pref_magnifying_glass_enabled;</string>
<string name="pref_magnifying_glass_enabled_summary">&pref_magnifying_glass_enabled_summary;</string>
<string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
<string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary;</string>

View File

@ -20,12 +20,12 @@ import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.tabqueue.TabQueueDispatcher;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
/**
* Process commands received from Sync clients.
@ -264,26 +264,27 @@ public class CommandProcessor {
}
final String ns = Context.NOTIFICATION_SERVICE;
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(ns);
// Create a Notification.
final int icon = R.drawable.flat_icon;
String notificationTitle = context.getString(R.string.sync_new_tab);
if (title != null) {
notificationTitle = notificationTitle.concat(": " + title);
}
final long when = System.currentTimeMillis();
Notification notification = new Notification(icon, notificationTitle, when);
notification.flags = Notification.FLAG_AUTO_CANCEL;
// Set pending intent associated with the notification.
Intent notificationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
notificationIntent.putExtra(TabQueueDispatcher.SKIP_TAB_QUEUE_FLAG, true);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, notificationTitle, uri, contentIntent);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.flat_icon);
builder.setContentTitle(notificationTitle);
builder.setWhen(System.currentTimeMillis());
builder.setAutoCancel(true);
builder.setContentIntent(contentIntent);
builder.setContentText(uri);
builder.setContentIntent(contentIntent);
// Send notification.
notificationManager.notify(currentId.getAndIncrement(), notification);
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(currentId.getAndIncrement(), builder.build());
}
}

View File

@ -70,14 +70,14 @@ public class TabStripView extends TwoWayView {
divider.getPadding(dividerPadding);
final int itemMargin =
resources.getDimensionPixelSize(R.dimen.new_tablet_tab_strip_item_margin);
resources.getDimensionPixelSize(R.dimen.tablet_tab_strip_item_margin);
setItemMargin(itemMargin);
animatorListener = new TabAnimatorListener();
fadingEdgePaint = new Paint();
fadingEdgeSize =
resources.getDimensionPixelOffset(R.dimen.new_tablet_tab_strip_fading_edge_size);
resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
adapter = new TabStripAdapter(context);
setAdapter(adapter);

View File

@ -88,10 +88,10 @@ class TabsGridLayout extends GridView
setClipToPadding(false);
final Resources resources = getResources();
mColumnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
mColumnWidth = resources.getDimensionPixelSize(R.dimen.tablet_tab_panel_column_width);
final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
final int paddingTop = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding_top);
final int padding = resources.getDimensionPixelSize(R.dimen.tablet_tab_panel_grid_padding);
final int paddingTop = resources.getDimensionPixelSize(R.dimen.tablet_tab_panel_grid_padding_top);
// Lets set double the top padding on the bottom so that the last row shows up properly!
// Your demise, GridView, cannot come fast enough.
@ -142,7 +142,7 @@ class TabsGridLayout extends GridView
final int removedHeight = getChildAt(0).getMeasuredHeight();
final int verticalSpacing =
getResources().getDimensionPixelOffset(R.dimen.new_tablet_tab_panel_grid_vspacing);
getResources().getDimensionPixelOffset(R.dimen.tablet_tab_panel_grid_vspacing);
ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
paddingAnimator.setDuration(ANIM_TIME_MS * 2);

View File

@ -36,7 +36,7 @@ class BrowserToolbarTablet extends BrowserToolbarTabletBase {
super(context, attrs);
forwardButtonTranslationWidth =
getResources().getDimensionPixelOffset(R.dimen.new_tablet_nav_button_width);
getResources().getDimensionPixelOffset(R.dimen.tablet_nav_button_width);
// The forward button is initially expanded (in the layout file)
// so translate it for start of the expansion animation; future

View File

@ -71,7 +71,7 @@ abstract class NavButton extends ShapedButton {
stateList.addState(PRIVATE_PRESSED_STATE_SET, getColorDrawable(R.color.placeholder_active_grey));
stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.toolbar_grey_pressed));
stateList.addState(PRIVATE_FOCUSED_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.new_tablet_highlight_focused));
stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.tablet_highlight_focused));
stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.tabs_tray_grey_pressed));
stateList.addState(EMPTY_STATE_SET, drawable);

View File

@ -30,6 +30,7 @@ import android.net.Uri;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Environment;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.net.ConnectivityManagerCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
@ -77,7 +78,7 @@ public class UpdateService extends IntentService {
private SharedPreferences mPrefs;
private NotificationManager mNotificationManager;
private NotificationManagerCompat mNotificationManager;
private ConnectivityManager mConnectivityManager;
private Builder mBuilder;
@ -142,7 +143,7 @@ public class UpdateService extends IntentService {
super.onCreate();
mPrefs = getSharedPreferences(PREFS_NAME, 0);
mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager = NotificationManagerCompat.from(this);
mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
mWifiLock = ((WifiManager)getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, PREFS_NAME);
@ -300,19 +301,19 @@ public class UpdateService extends IntentService {
sendCheckUpdateResult(CheckUpdateResult.AVAILABLE);
// We aren't autodownloading here, so prompt to start the update
Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE);
notificationIntent.setClass(this, UpdateService.class);
PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(this, getResources().getString(R.string.updater_start_title),
getResources().getString(R.string.updater_start_select),
contentIntent);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_status_logo);
builder.setWhen(System.currentTimeMillis());
builder.setAutoCancel(true);
builder.setContentTitle(getString(R.string.updater_start_title));
builder.setContentText(getString(R.string.updater_start_select));
builder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, notification);
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
return;
}
@ -332,20 +333,22 @@ public class UpdateService extends IntentService {
applyUpdate(pkg);
} else {
// Prompt to apply the update
Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
notificationIntent.setClass(this, UpdateService.class);
notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath());
PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title),
getResources().getString(R.string.updater_apply_select),
contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, notification);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_status_logo);
builder.setWhen(System.currentTimeMillis());
builder.setAutoCancel(true);
builder.setContentTitle(getString(R.string.updater_apply_title));
builder.setContentText(getString(R.string.updater_apply_select));
builder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
}
}
@ -475,18 +478,18 @@ public class UpdateService extends IntentService {
}
private void showDownloadFailure() {
Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE);
notificationIntent.setClass(this, UpdateService.class);
PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title_failed),
getResources().getString(R.string.updater_downloading_retry),
contentIntent);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_status_logo);
builder.setWhen(System.currentTimeMillis());
builder.setContentTitle(getString(R.string.updater_downloading_title_failed));
builder.setContentText(getString(R.string.updater_downloading_retry));
builder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, notification);
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
}
private boolean deleteUpdatePackage(String path) {

View File

@ -50,6 +50,37 @@ dependencies {
androidTestCompile 'com.jayway.android.robotium:robotium-solo:4.3.1'
}
task syncOmnijarFromDistDir(type: Sync) {
into("${project.buildDir}/generated/omnijar")
from("${topobjdir}/dist/fennec/assets") {
include 'omni.ja'
}
}
task checkLibsExistInDistDir<< {
if (syncLibsFromDistDir.source.empty) {
throw new GradleException("Required JNI libraries not found in ${topobjdir}/dist/fennec/lib. Have you built and packaged?")
}
}
task syncLibsFromDistDir(type: Sync, dependsOn: checkLibsExistInDistDir) {
into("${project.buildDir}/generated/jniLibs")
from("${topobjdir}/dist/fennec/lib")
}
task checkAssetsExistInDistDir<< {
if (syncAssetsFromDistDir.source.empty) {
throw new GradleException("Required assets not found in ${topobjdir}/dist/fennec/assets. Have you built and packaged?")
}
}
task syncAssetsFromDistDir(type: Sync, dependsOn: checkAssetsExistInDistDir) {
into("${project.buildDir}/generated/assets")
from("${topobjdir}/dist/fennec/assets") {
exclude 'omni.ja'
}
}
/**
* We want to expose the JSM files and chrome content to IDEs; the omnijar
* project does this. In addition, the :omnijar:buildOmnijar task builds a new
@ -71,8 +102,15 @@ android.applicationVariants.all { variant ->
}
def buildOmnijarTask = project(':omnijar').tasks.getByName('buildOmnijar')
syncOmnijarFromDistDir.dependsOn buildOmnijarTask
def generateAssetsTask = tasks.findByName("generate${name.capitalize()}Assets")
generateAssetsTask.dependsOn buildOmnijarTask
generateAssetsTask.dependsOn syncOmnijarFromDistDir
generateAssetsTask.dependsOn syncLibsFromDistDir
generateAssetsTask.dependsOn syncAssetsFromDistDir
android.sourceSets.debug.assets.srcDir syncOmnijarFromDistDir.destinationDir
android.sourceSets.debug.assets.srcDir syncAssetsFromDistDir.destinationDir
android.sourceSets.debug.jniLibs.srcDir syncLibsFromDistDir.destinationDir
}
apply plugin: 'spoon'

View File

@ -77,10 +77,6 @@ dependencies {
compile project(':thirdparty')
}
android.libraryVariants.all { variant ->
variant.checkManifest.dependsOn rootProject.generateCodeAndResources
}
apply plugin: 'idea'
idea {

View File

@ -59,3 +59,17 @@ task generateCodeAndResources(type:Exec) {
}
}
}
afterEvaluate {
subprojects {
if (!hasProperty('android')) {
return
}
android.applicationVariants.all {
checkManifest.dependsOn rootProject.generateCodeAndResources
}
android.libraryVariants.all {
checkManifest.dependsOn rootProject.generateCodeAndResources
}
}
}

View File

@ -21,21 +21,30 @@ android {
sourceSets {
main {
java {
srcDir "${topsrcdir}/mobile/android/base/adjust"
srcDir "${project.buildDir}/generated/source/java"
srcDir 'src/adjust/java'
if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
exclude 'StubAdjustHelper.java'
exclude 'org/mozilla/gecko/adjust/StubAdjustHelper.java'
} else {
exclude 'AdjustHelper.java'
exclude 'org/mozilla/gecko/adjust/AdjustHelper.java'
}
}
}
}
}
android.libraryVariants.all { variant ->
variant.checkManifest.dependsOn rootProject.generateCodeAndResources
task syncGeneratedSources(type: Sync) {
into("${project.buildDir}/generated/source/java")
from("${topobjdir}/mobile/android/base/generated/preprocessed")
}
android.libraryVariants.all { variant ->
// variant does not expose its generate sources task.
def name = variant.buildType.name
def generateSourcesTask = tasks.findByName("generate${name.capitalize()}Sources")
generateSourcesTask.dependsOn syncGeneratedSources
}
dependencies {
if (mozconfig.substs.MOZ_INSTALL_TRACKING) {

View File

@ -17,10 +17,26 @@ android {
lintOptions {
abortOnError false
}
sourceSets {
main {
res {
srcDir "${project.buildDir}/generated/source/preprocessed_resources"
}
}
}
}
task syncPreprocessedResources(type: Sync) {
into("${project.buildDir}/generated/source/preprocessed_resources")
from("${topobjdir}/mobile/android/base/res")
}
android.libraryVariants.all { variant ->
variant.checkManifest.dependsOn rootProject.generateCodeAndResources
// variant does not expose its generate debug res values task.
def name = variant.buildType.name
def generateResValuesTask = tasks.findByName("generate${name.capitalize()}ResValues")
generateResValuesTask.dependsOn syncPreprocessedResources
}
dependencies {

View File

@ -95,11 +95,10 @@ class MachCommands(MachCommandBase):
srcdir('preprocessed_code/build.gradle', 'mobile/android/gradle/preprocessed_code/build.gradle')
srcdir('preprocessed_code/src/main/AndroidManifest.xml', 'mobile/android/gradle/preprocessed_code/AndroidManifest.xml')
objdir('preprocessed_code/src/main/java', 'mobile/android/base/generated/preprocessed')
srcdir('preprocessed_code/src/adjust/java/org/mozilla/gecko/adjust', 'mobile/android/base/adjust')
srcdir('preprocessed_resources/build.gradle', 'mobile/android/gradle/preprocessed_resources/build.gradle')
srcdir('preprocessed_resources/src/main/AndroidManifest.xml', 'mobile/android/gradle/preprocessed_resources/AndroidManifest.xml')
objdir('preprocessed_resources/src/main/res', 'mobile/android/base/res')
srcdir('thirdparty/build.gradle', 'mobile/android/gradle/thirdparty/build.gradle')
srcdir('thirdparty/src/main/AndroidManifest.xml', 'mobile/android/gradle/thirdparty/AndroidManifest.xml')
@ -120,8 +119,6 @@ class MachCommands(MachCommandBase):
objdir('app/src/androidTest/AndroidManifest.xml', 'build/mobile/robocop/AndroidManifest.xml')
srcdir('app/src/androidTest/res', 'build/mobile/robocop/res')
srcdir('app/src/androidTest/assets', 'mobile/android/tests/browser/robocop/assets')
objdir('app/src/debug/assets', 'dist/fennec/assets')
objdir('app/src/debug/jniLibs', 'dist/fennec/lib')
# Test code.
srcdir('app/src/robocop_harness/org/mozilla/gecko', 'build/mobile/robocop')
srcdir('app/src/robocop/org/mozilla/gecko/tests', 'mobile/android/tests/browser/robocop')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

View File

@ -51,10 +51,7 @@ chrome.jar:
skin/images/textfield.png (images/textfield.png)
skin/images/5stars.png (images/5stars.png)
skin/images/addons-32.png (images/addons-32.png)
skin/images/amo-logo.png (images/amo-logo.png)
skin/images/arrowleft-16.png (images/arrowleft-16.png)
skin/images/arrowright-16.png (images/arrowright-16.png)
skin/images/arrowdown-16.png (images/arrowdown-16.png)
skin/images/arrowup-16.png (images/arrowup-16.png)
skin/images/blocked-warning.png (images/blocked-warning.png)
@ -65,7 +62,6 @@ chrome.jar:
skin/images/checkbox_unchecked_disabled.png (images/checkbox_unchecked_disabled.png)
skin/images/checkbox_unchecked_pressed.png (images/checkbox_unchecked_pressed.png)
skin/images/chevron.png (images/chevron.png)
skin/images/default-app-icon.png (images/default-app-icon.png)
skin/images/dropmarker.svg (images/dropmarker.svg)
skin/images/dropmarker-right.svg (images/dropmarker-right.svg)
skin/images/errorpage-warning.png (images/errorpage-warning.png)
@ -73,8 +69,6 @@ chrome.jar:
skin/images/fullscreen-hdpi.png (images/fullscreen-hdpi.png)
skin/images/grey-caution.svg (images/grey-caution.svg)
skin/images/certerror-warning.png (images/certerror-warning.png)
skin/images/errorpage-larry-white.png (images/errorpage-larry-white.png)
skin/images/errorpage-larry-black.png (images/errorpage-larry-black.png)
skin/images/throbber.png (images/throbber.png)
skin/images/search-clear-30.png (images/search-clear-30.png)
skin/images/play-hdpi.png (images/play-hdpi.png)

View File

@ -2420,6 +2420,36 @@ ObjectClient.prototype = {
return aPacket;
}
}),
/**
* Request the stack to the promise's fulfillment point.
*/
getPromiseFulfillmentStack: DebuggerClient.requester({
type: "fulfillmentStack"
}, {
before: function(packet) {
if (this._grip.class !== "Promise") {
throw new Error("getPromiseFulfillmentStack is only valid for " +
"promise grips.");
}
return packet;
}
}),
/**
* Request the stack to the promise's rejection point.
*/
getPromiseRejectionStack: DebuggerClient.requester({
type: "rejectionStack"
}, {
before: function(packet) {
if (this._grip.class !== "Promise") {
throw new Error("getPromiseRejectionStack is only valid for " +
"promise grips.");
}
return packet;
}
})
};
/**

View File

@ -119,6 +119,8 @@ HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "AsyncUtils", "devtools/toolkit/async-utils");
loader.lazyGetter(this, "DOMParser", function() {
return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
});
@ -624,16 +626,12 @@ var NodeActor = exports.NodeActor = protocol.ActorClass({
* transfered in the longstring back to the client will be that much smaller
*/
getImageData: method(function(maxDim) {
// imageToImageData may fail if the node isn't an image
try {
let imageData = imageToImageData(this.rawNode, maxDim);
return promise.resolve({
return imageToImageData(this.rawNode, maxDim).then(imageData => {
return {
data: LongStringActor(this.conn, imageData.data),
size: imageData.size
});
} catch(e) {
return promise.reject(new Error("Image not available"));
}
};
});
}, {
request: {maxDim: Arg(0, "nullable:number")},
response: RetVal("imageData")
@ -3645,39 +3643,16 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({
* transfered in the longstring back to the client will be that much smaller
*/
getImageDataFromURL: method(function(url, maxDim) {
let deferred = promise.defer();
let img = new this.window.Image();
// On load, get the image data and send the response
img.onload = () => {
// imageToImageData throws an error if the image is missing
try {
let imageData = imageToImageData(img, maxDim);
deferred.resolve({
data: LongStringActor(this.conn, imageData.data),
size: imageData.size
});
} catch (e) {
deferred.reject(new Error("Image " + url+ " not available"));
}
}
// If the URL doesn't point to a resource, reject
img.onerror = () => {
deferred.reject(new Error("Image " + url+ " not available"));
}
// If the request hangs for too long, kill it to avoid queuing up other requests
// to the same actor, except if we're running tests
if (!DevToolsUtils.testing) {
this.window.setTimeout(() => {
deferred.reject(new Error("Image " + url + " could not be retrieved in time"));
}, IMAGE_FETCHING_TIMEOUT);
}
img.src = url;
return deferred.promise;
// imageToImageData waits for the image to load.
return imageToImageData(img, maxDim).then(imageData => {
return {
data: LongStringActor(this.conn, imageData.data),
size: imageData.size
};
});
}, {
request: {url: Arg(0), maxDim: Arg(1, "nullable:number")},
response: RetVal("imageData")
@ -3922,20 +3897,84 @@ function allAnonymousContentTreeWalkerFilter(aNode) {
}
/**
* Given an image DOMNode, return the image data-uri.
* @param {DOMNode} node The image node
* @param {Number} maxDim Optionally pass a maximum size you want the longest
* side of the image to be resized to before getting the image data.
* @return {Object} An object containing the data-uri and size-related information
* {data: "...", size: {naturalWidth: 400, naturalHeight: 300, resized: true}}
* @throws an error if the node isn't an image or if the image is missing
* Returns a promise that is settled once the given HTMLImageElement has
* finished loading.
*
* @param {HTMLImageElement} image - The image element.
* @param {Number} timeout - Maximum amount of time the image is allowed to load
* before the waiting is aborted. Ignored if DevToolsUtils.testing is set.
*
* @return {Promise} that is fulfilled once the image has loaded. If the image
* fails to load or the load takes too long, the promise is rejected.
*/
function imageToImageData(node, maxDim) {
let isImg = node.tagName.toLowerCase() === "img";
let isCanvas = node.tagName.toLowerCase() === "canvas";
function ensureImageLoaded(image, timeout) {
let { HTMLImageElement } = image.ownerDocument.defaultView;
if (!(image instanceof HTMLImageElement)) {
return promise.reject("image must be an HTMLImageELement");
}
if (image.complete) {
// The image has already finished loading.
return promise.resolve();
}
// This image is still loading.
let onLoad = AsyncUtils.listenOnce(image, "load");
// Reject if loading fails.
let onError = AsyncUtils.listenOnce(image, "error").then(() => {
return promise.reject("Image '" + image.src + "' failed to load.");
});
// Don't timeout when testing. This is never settled.
let onAbort = new promise(() => {});
if (!DevToolsUtils.testing) {
// Tests are not running. Reject the promise after given timeout.
onAbort = DevToolsUtils.waitForTime(timeout).then(() => {
return promise.reject("Image '" + image.src + "' took too long to load.");
});
}
// See which happens first.
return promise.race([onLoad, onError, onAbort]);
}
/**
* Given an <img> or <canvas> element, return the image data-uri. If @param node
* is an <img> element, the method waits a while for the image to load before
* the data is generated. If the image does not finish loading in a reasonable
* time (IMAGE_FETCHING_TIMEOUT milliseconds) the process aborts.
*
* @param {HTMLImageElement|HTMLCanvasElement} node - The <img> or <canvas>
* element, or Image() object. Other types cause the method to reject.
* @param {Number} maxDim - Optionally pass a maximum size you want the longest
* side of the image to be resized to before getting the image data.
* @return {Promise} A promise that is fulfilled with an object containing the
* data-uri and size-related information:
* { data: "...",
* size: {
* naturalWidth: 400,
* naturalHeight: 300,
* resized: true }
* }.
*
* If something goes wrong, the promise is rejected.
*/
let imageToImageData = Task.async(function* (node, maxDim) {
let { HTMLCanvasElement, HTMLImageElement } = node.ownerDocument.defaultView;
let isImg = node instanceof HTMLImageElement;
let isCanvas = node instanceof HTMLCanvasElement;
if (!isImg && !isCanvas) {
return null;
throw "node is not a <canvas> or <img> element.";
}
if (isImg) {
// Ensure that the image is ready.
yield ensureImageLoaded(node, IMAGE_FETCHING_TIMEOUT);
}
// Get the image resize ratio if a maxDim was provided
@ -3973,7 +4012,7 @@ function imageToImageData(node, maxDim) {
resized: resizeRatio !== 1
}
}
}
});
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);

View File

@ -577,8 +577,68 @@ ObjectActor.prototype = {
},
/**
* Helper function for onAllocationStack which fetches the source location
* for a SavedFrame stack.
* Handle a protocol request to get the fulfillment stack of a promise.
*/
onFulfillmentStack: function() {
if (this.obj.class != "Promise") {
return { error: "objectNotPromise",
message: "'fulfillmentStack' request is only valid for " +
"object grips with a 'Promise' class." };
}
let rawPromise = this.obj.unsafeDereference();
let stack = PromiseDebugging.getFullfillmentStack(rawPromise);
let fulfillmentStacks = [];
while (stack) {
if (stack.source) {
let source = this._getSourceOriginalLocation(stack);
if (source) {
fulfillmentStacks.push(source);
}
}
stack = stack.parent;
}
return Promise.all(fulfillmentStacks).then(stacks => {
return { fulfillmentStack: stacks };
});
},
/**
* Handle a protocol request to get the rejection stack of a promise.
*/
onRejectionStack: function() {
if (this.obj.class != "Promise") {
return { error: "objectNotPromise",
message: "'rejectionStack' request is only valid for " +
"object grips with a 'Promise' class." };
}
let rawPromise = this.obj.unsafeDereference();
let stack = PromiseDebugging.getRejectionStack(rawPromise);
let rejectionStacks = [];
while (stack) {
if (stack.source) {
let source = this._getSourceOriginalLocation(stack);
if (source) {
rejectionStacks.push(source);
}
}
stack = stack.parent;
}
return Promise.all(rejectionStacks).then(stacks => {
return { rejectionStack: stacks };
});
},
/**
* Helper function for fetching the source location of a SavedFrame stack.
*
* @param SavedFrame stack
* The promise allocation stack frame
* @return object
@ -625,7 +685,9 @@ ObjectActor.prototype.requestTypes = {
"release": ObjectActor.prototype.onRelease,
"scope": ObjectActor.prototype.onScope,
"dependentPromises": ObjectActor.prototype.onDependentPromises,
"allocationStack": ObjectActor.prototype.onAllocationStack
"allocationStack": ObjectActor.prototype.onAllocationStack,
"fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
"rejectionStack": ObjectActor.prototype.onRejectionStack
};
/**

View File

@ -8,6 +8,7 @@ support-files =
director-helpers.js
hello-actor.js
inspector_getImageData.html
inspector-delay-image-response.sjs
inspector-helpers.js
inspector-styles-data.css
inspector-styles-data.html
@ -57,6 +58,10 @@ skip-if = buildapp == 'mulet'
[test_inspector-dead-nodes.html]
[test_inspector_getImageData.html]
skip-if = buildapp == 'mulet'
[test_inspector_getImageDataFromURL.html]
skip-if = buildapp == 'mulet'
[test_inspector_getImageData-wait-for-load.html]
skip-if = buildapp == 'mulet'
[test_inspector_getNodeFromActor.html]
[test_inspector-hide.html]
[test_inspector-insert.html]

View File

@ -0,0 +1,42 @@
/**
* Adapted from https://dxr.mozilla.org/mozilla-central/rev/
* 4e883591bb5dff021c108d3e30198a99547eed1e/layout/reftests/backgrounds/
* delay-image-response.sjs
*/
"use strict";
// A 1x1 PNG image.
// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
"ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
// To avoid GC.
let timer = null;
function handleRequest(request, response) {
let query = {};
request.queryString.split("&").forEach(function(val) {
let [name, value] = val.split("=");
query[name] = unescape(value);
});
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "image/png", false);
// If there is no delay, we write the image and leave.
if (!("delay" in query)) {
response.write(IMAGE);
return;
}
// If there is a delay, we create a timer which, when it fires, will write
// image and leave.
response.processAsync();
const nsITimer = Components.interfaces.nsITimer;
timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
timer.initWithCallback(function() {
response.write(IMAGE);
response.finish();
}, query.delay, nsITimer.TYPE_ONE_SHOT);
}

View File

@ -1,6 +1,7 @@
<html>
<head>
<body>
<img class="custom">
<img class="big-horizontal" src="large-image.jpg" style="width:500px;" />
<canvas class="big-vertical" style="width:500px;"></canvas>
<img class="small" src="small-image.gif"></img>

View File

@ -0,0 +1,136 @@
<!DOCTYPE HTML>
<html>
<!--
Tests for InspectorActor.getImageData() in following cases:
* Image takes too long to load (the method rejects after a timeout).
* Image is loading when the method is called and the load finishes before
timeout.
* Image fails to load.
https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1192536</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const wasTesting = DevToolsUtils.testing;
SimpleTest.registerCleanupFunction(() => DevToolsUtils.testing = wasTesting);
const PATH = "http://mochi.test:8888/chrome/toolkit/devtools/server/tests/mochitest/";
const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gImg = null;
var gNodeFront = null;
var gWalker = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
let {InspectorFront} = require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
gWalker = walker;
return walker.querySelector(gWalker.rootNode, "img.custom").then(img => {
gNodeFront = img;
gImg = doc.querySelector("img.custom");
ok(gNodeFront, "Got the image NodeFront.");
ok(gImg, "Got the image Node.");
});
}).then(runNextTest));
});
});
addTest(function testTimeout() {
info("Testing that the method aborts if the image takes too long to load.");
// imageToImageData() only times out when DTU.testing is not set.
DevToolsUtils.testing = false;
gImg.src = TIMEOUT_IMAGE;
info("Calling getImageData().");
ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest);
});
addTest(function testNonExistentImage() {
info("Testing that non-existent image causes a rejection.");
// This test shouldn't hit the timeout.
DevToolsUtils.testing = true;
gImg.src = NONEXISTENT_IMAGE;
info("Calling getImageData().");
ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest);
});
addTest(function testDelayedImage() {
info("Testing that the method waits for an image to load.");
// This test shouldn't hit the timeout.
DevToolsUtils.testing = true;
gImg.src = DELAYED_IMAGE;
info("Calling getImageData().");
checkImageData(gNodeFront.getImageData()).then(runNextTest);
});
addTest(function cleanup() {
delete gImg;
delete gNodeFront
delete gWalker;
runNextTest();
});
/**
* Asserts that the given promise rejects.
*/
function ensureRejects(promise, desc) {
return promise.then(() => {
ok(false, desc + ": promise resolved unexpectedly.");
}, () => {
ok(true, desc + ": promise rejected as expected.");
});
}
/**
* Waits for the call to getImageData() the resolve and checks that the image
* size is reported correctly.
*/
function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
return promise.then(({ size }) => {
is(size.naturalWidth, width, "The width is correct.");
is(size.naturalHeight, height, "The height is correct.");
});
}
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -95,10 +95,27 @@ addTest(function testSmallImage() {
});
});
addTest(function testNonImgOrCanvasElements() {
gWalker.querySelector(gWalker.rootNode, "body").then(body => {
ensureRejects(body.getImageData(), "Invalid element").then(runNextTest);
});
});
addTest(function cleanup() {
delete gWalker;
runNextTest();
});
/**
* Asserts that the given promise rejects.
*/
function ensureRejects(promise, desc) {
return promise.then(() => {
ok(false, desc + ": promise resolved unexpectedly.");
}, () => {
ok(true, desc + ": promise rejected as expected.");
});
}
</script>
</head>
<body>

View File

@ -0,0 +1,114 @@
<!DOCTYPE HTML>
<html>
<!--
Tests for InspectorActor.getImageDataFromURL() in following cases:
* Normal case, image loads after a small delay.
* Image takes too long to load (the method rejects after a timeout).
* Image fails to load.
https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1192536</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const wasTesting = DevToolsUtils.testing;
SimpleTest.registerCleanupFunction(() => DevToolsUtils.testing = wasTesting);
const PATH = "http://mochi.test:8888/chrome/toolkit/devtools/server/tests/mochitest/";
const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gInspector = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
let {InspectorFront} = require("devtools/server/actors/inspector");
gInspector = InspectorFront(client, tab);
runNextTest();
});
});
addTest(function testTimeout() {
info("Testing that the method aborts if the image takes too long to load.");
// imageToImageData() only times out when DTU.testing is not set.
DevToolsUtils.testing = false;
ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE),
"Image that loads for too long").then(runNextTest);
});
addTest(function testNonExistentImage() {
info("Testing that non-existent image causes a rejection.");
// This test shouldn't hit the timeout.
DevToolsUtils.testing = true;
ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE),
"Non-existent image").then(runNextTest);
});
addTest(function testNormalImage() {
info("Testing that the method waits for an image to load.");
// This test shouldn't hit the timeout.
DevToolsUtils.testing = true;
checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest);
});
addTest(function cleanup() {
delete gInspector;
runNextTest();
});
/**
* Asserts that the given promise rejects.
*/
function ensureRejects(promise, desc) {
return promise.then(() => {
ok(false, desc + ": promise resolved unexpectedly.");
}, () => {
ok(true, desc + ": promise rejected as expected.");
});
}
/**
* Waits for the call to getImageData() the resolve and checks that the image
* size is reported correctly.
*/
function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
return promise.then(({ size }) => {
is(size.naturalWidth, width, "The width is correct.");
is(size.naturalHeight, height, "The height is correct.");
});
}
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -143,6 +143,13 @@ this.AppConstants = Object.freeze({
false,
#endif
MOZ_VERIFY_MAR_SIGNATURE:
#ifdef MOZ_VERIFY_MAR_SIGNATURE
true,
#else
false,
#endif
MOZ_MAINTENANCE_SERVICE:
#ifdef MOZ_MAINTENANCE_SERVICE
true,

View File

@ -7663,9 +7663,17 @@ DirectoryInstallLocation.prototype = {
getTrashDir: function DirInstallLocation_getTrashDir() {
let trashDir = this._directory.clone();
trashDir.append(DIR_TRASH);
if (trashDir.exists())
recursiveRemove(trashDir);
trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let trashDirExists = trashDir.exists();
try {
if (trashDirExists)
recursiveRemove(trashDir);
trashDirExists = false;
} catch (e) {
logger.warn("Failed to remove trash directory", e);
}
if (!trashDirExists)
trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
return trashDir;
},

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
startupManager();
run_next_test();
}
add_task(function* () {
let profileDir = OS.Constants.Path.profileDir;
let trashDir = OS.Path.join(profileDir, "extensions", "trash");
let testFile = OS.Path.join(trashDir, "test.txt");
yield OS.File.makeDir(trashDir, {
from: profileDir,
ignoreExisting: true
});
let trashDirExists = yield OS.File.exists(trashDir);
ok(trashDirExists, "trash directory should have been created");
let file = yield OS.File.open(testFile, {create: true}, {winShare: 0});
let fileExists = yield OS.File.exists(testFile);
ok(fileExists, "test.txt should have been created in " + trashDir);
yield promiseInstallAllFiles([do_get_addon("test_install1")]);
yield promiseRestartManager();
fileExists = yield OS.File.exists(testFile);
ok(fileExists, "test.txt still exists");
yield file.close();
yield OS.File.removeDir(OS.Path.join(OS.Constants.Path.profileDir, "extensions"));
yield promiseShutdownManager();
});

View File

@ -288,3 +288,5 @@ run-sequentially = Uses global XCurProcD dir.
[test_sourceURI.js]
[test_webextension.js]
[test_bootstrap_globals.js]
[test_bug1180901.js]
skip-if = os != "win"

View File

@ -28,4 +28,5 @@ skip-if = appname != "firefox"
[test_XPIStates.js]
[include:xpcshell-shared.ini]

View File

@ -3872,6 +3872,13 @@ Downloader.prototype = {
}
LOG("Downloader:_verifyDownload downloaded size == expected size.");
// The hash check is not necessary when mar signatures are used to verify
// the downloaded mar file.
if (AppConstants.MOZ_VERIFY_MAR_SIGNATURE) {
return true;
}
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: update check, basic, download, and errors (partial patch with an invalid hash)"
<window title="Update Wizard pages: update check, basic, download, and errors (partial patch with an invalid size)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -35,7 +35,7 @@ function runTest() {
debugDump("entering");
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&partialPatchOnly=1" +
"&invalidPartialHash=1" + getVersionParams();
"&invalidPartialSize=1" + getVersionParams();
setUpdateURLOverride(url);
gUP.checkForUpdates();

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: update check, basic, download, and errors (complete patch with an invalid hash)"
<window title="Update Wizard pages: update check, basic, download, and errors (complete patch with an invalid size)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -35,7 +35,7 @@ function runTest() {
debugDump("entering");
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&completePatchOnly=1" +
"&invalidCompleteHash=1" + getVersionParams();
"&invalidCompleteSize=1" + getVersionParams();
setUpdateURLOverride(url);
gUP.checkForUpdates();

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: update check, basic, download, and errors (partial and complete patches with invalid hashes)"
<window title="Update Wizard pages: update check, basic, download, and errors (partial and complete patches with invalid sizes)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -34,8 +34,8 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialHash=1" +
"&invalidCompleteHash=1" + getVersionParams();
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialSize=1" +
"&invalidCompleteSize=1" + getVersionParams();
setUpdateURLOverride(url);
gUP.checkForUpdates();

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: update check, basic, download, and finished (partial patch with an invalid hash and successful complete patch)"
<window title="Update Wizard pages: update check, basic, download, and finished (partial patch with an invalid size and successful complete patch)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -34,7 +34,7 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialHash=1" +
let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialSize=1" +
getVersionParams();
setUpdateURLOverride(url);

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: errors (partial patch with an invalid hash)"
<window title="Update Wizard pages: errors (partial patch with an invalid size)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -27,7 +27,7 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let patches = getLocalPatchString("partial", null, null, "1234", null, null,
let patches = getLocalPatchString("partial", null, null, null, "1234", null,
STATE_DOWNLOADING);
let updates = getLocalUpdateString(patches, null, null, null,
Services.appinfo.version,

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: errors (complete patch with an invalid hash)"
<window title="Update Wizard pages: errors (complete patch with an invalid size)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -27,7 +27,7 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let patches = getLocalPatchString("complete", null, null, "1234", null, null,
let patches = getLocalPatchString("complete", null, null, null, "1234", null,
STATE_DOWNLOADING);
let updates = getLocalUpdateString(patches, null, null, null,
Services.appinfo.version,

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: errors (partial and complete patches with invalid hashes)"
<window title="Update Wizard pages: errors (partial and complete patches with invalid sizes)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -27,9 +27,9 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let patches = getLocalPatchString("partial", null, null, "1234", null, null,
let patches = getLocalPatchString("partial", null, null, null, "1234", null,
STATE_DOWNLOADING) +
getLocalPatchString("complete", null, null, "1234", null,
getLocalPatchString("complete", null, null, null, "1234",
"false");
let updates = getLocalUpdateString(patches, null, null, null,
Services.appinfo.version,

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window title="Update Wizard pages: finishedBackground (partial patch with an invalid hash and successful complete patch)"
<window title="Update Wizard pages: finishedBackground (partial patch with an invalid size and successful complete patch)"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="runTestDefault();">
<script type="application/javascript"
@ -27,7 +27,7 @@ const TESTS = [ {
function runTest() {
debugDump("entering");
let patches = getLocalPatchString("partial", null, null, "1234", null, null,
let patches = getLocalPatchString("partial", null, null, null, "1234", null,
STATE_DOWNLOADING) +
getLocalPatchString("complete", null, null, null, null,
"false");

View File

@ -42,7 +42,7 @@ function runTest() {
let patches = getLocalPatchString("partial", null, null, null, null, null,
STATE_PENDING) +
getLocalPatchString("complete", slowDownloadURL, "MD5",
"1234cd43a1c77e30191c53a329a3f99d", null,
null, "1234",
"false");
let updates = getLocalUpdateString(patches, null, null, null,
Services.appinfo.version,

View File

@ -112,18 +112,18 @@ function handleRequest(aRequest, aResponse) {
return;
}
let hash;
let size;
let patches = "";
if (!params.partialPatchOnly) {
hash = SHA512_HASH_SIMPLE_MAR + (params.invalidCompleteHash ? "e" : "");
size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : "");
patches += getRemotePatchString("complete", SERVICE_URL, "SHA512",
hash, SIZE_SIMPLE_MAR);
SHA512_HASH_SIMPLE_MAR, size);
}
if (!params.completePatchOnly) {
hash = SHA512_HASH_SIMPLE_MAR + (params.invalidPartialHash ? "e" : "");
size = SIZE_SIMPLE_MAR + (params.invalidPartialSize ? "1" : "");
patches += getRemotePatchString("partial", SERVICE_URL, "SHA512",
hash, SIZE_SIMPLE_MAR);
SHA512_HASH_SIMPLE_MAR, size);
}
let type = params.type ? params.type : "major";

View File

@ -23,7 +23,13 @@ function run_test() {
// The mock XMLHttpRequest is MUCH faster
overrideXHR(callHandleEvent);
standardInit();
do_execute_soon(run_test_pt1);
// Only perform the non hash check tests when mar signing is enabled since the
// update service doesn't perform hash checks when mar signing is enabled.
if (IS_MAR_CHECKS_ENABLED) {
do_execute_soon(run_test_pt11);
} else {
do_execute_soon(run_test_pt1);
}
}
// The HttpServer must be stopped before calling do_test_finished