Merge m-c to b2g-inbound a=merge

This commit is contained in:
Wes Kocher 2014-10-17 20:15:49 -07:00
Родитель 89999b38b5 3659643e8e
Коммит 3c1f665b48
80 изменённых файлов: 4257 добавлений и 2925 удалений

Просмотреть файл

@ -287,9 +287,8 @@ let gSyncUI = {
// Commands
doSync: function SUI_doSync() {
let needsSetup = this._needsSetup();
let loginFailed = this._loginFailed();
if (!(loginFailed || needsSetup)) {
if (!needsSetup) {
setTimeout(function () Weave.Service.errorHandler.syncAndReportErrors(), 0);
}

Просмотреть файл

@ -448,10 +448,13 @@ this.GoogleImporter.prototype = {
if (phoneNodes.length) {
contact.tel = [];
for (let [,phoneNode] of Iterator(phoneNodes)) {
let phoneNumber = phoneNode.hasAttribute("uri") ?
phoneNode.getAttribute("uri").replace("tel:", "") :
phoneNode.firstChild.nodeValue;
contact.tel.push({
pref: (phoneNode.getAttribute("primary") == "true"),
type: [getFieldType(phoneNode)],
value: phoneNode.getAttribute("uri").replace("tel:", "")
value: phoneNumber
});
}
}

Просмотреть файл

@ -790,16 +790,85 @@ let LoopContactsInternal = Object.freeze({
/**
* Search through the data store for contacts that match a certain (sub-)string.
* NB: The current implementation is very simple, naive if you will; we fetch
* _all_ the contacts via `getAll()` and iterate over all of them to find
* the contacts matching the supplied query (brute-force search in
* exponential time).
*
* @param {String} query Needle to search for in our haystack of contacts
* @param {Object} query Needle to search for in our haystack of contacts
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be an `Array` of contact objects, if successfull.
*
* Example:
* LoopContacts.search({
* q: "foo@bar.com",
* field: "email" // 'email' is the default.
* }, function(err, contacts) {
* if (err) {
* throw err;
* }
* console.dir(contacts);
* });
*/
search: function(query, callback) {
//TODO in bug 1037114.
callback(new Error("Not implemented yet!"));
if (!("q" in query) || !query.q) {
callback(new Error("Nothing to search for. 'q' is required."));
return;
}
if (!("field" in query)) {
query.field = "email";
}
let queryValue = query.q;
if (query.field == "tel") {
queryValue = queryValue.replace(/[\D]+/g, "");
}
const checkForMatch = function(fieldValue) {
if (typeof fieldValue == "string") {
if (query.field == "tel") {
return fieldValue.replace(/[\D]+/g, "").endsWith(queryValue);
}
return fieldValue == queryValue;
}
if (typeof fieldValue == "number" || typeof fieldValue == "boolean") {
return fieldValue == queryValue;
}
if ("value" in fieldValue) {
return checkForMatch(fieldValue.value);
}
return false;
};
let foundContacts = [];
this.getAll((err, contacts) => {
if (err) {
callback(err);
return;
}
for (let contact of contacts) {
let matchWith = contact[query.field];
if (!matchWith) {
continue;
}
// Many fields are defined as Arrays.
if (Array.isArray(matchWith)) {
for (let fieldValue of matchWith) {
if (checkForMatch(fieldValue)) {
foundContacts.push(contact);
break;
}
}
} else if (checkForMatch(matchWith)) {
foundContacts.push(contact);
}
}
callback(null, foundContacts);
});
}
});

Просмотреть файл

@ -23,6 +23,8 @@ const LOOP_SESSION_TYPE = {
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
const PREF_LOG_LEVEL = "loop.debug.loglevel";
const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
@ -56,6 +58,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "HawkClient",
XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
"resource://services-common/hawkrequest.js");
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
"resource:///modules/loop/LoopContacts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
"resource:///modules/loop/LoopStorage.jsm");
@ -786,17 +791,46 @@ let MozLoopServiceInternal = {
* Starts a call, saves the call data, and opens a chat window.
*
* @param {Object} callData The data associated with the call including an id.
* @param {Boolean} conversationType Whether or not the call is "incoming"
* or "outgoing"
* @param {String} conversationType Whether or not the call is "incoming"
* or "outgoing"
*/
_startCall: function(callData, conversationType) {
this.callsData.inUse = true;
this.callsData.data = callData;
this.openChatWindow(
null,
// No title, let the page set that, to avoid flickering.
"",
"about:loopconversation#" + conversationType + "/" + callData.callId);
const openChat = () => {
this.callsData.inUse = true;
this.callsData.data = callData;
this.openChatWindow(
null,
// No title, let the page set that, to avoid flickering.
"",
"about:loopconversation#" + conversationType + "/" + callData.callId);
};
if (conversationType == "incoming" && ("callerId" in callData) &&
EMAIL_OR_PHONE_RE.test(callData.callerId)) {
LoopContacts.search({
q: callData.callerId,
field: callData.callerId.contains("@") ? "email" : "tel"
}, (err, contacts) => {
if (err) {
// Database error, helas!
openChat();
return;
}
for (let contact of contacts) {
if (contact.blocked) {
// Blocked! Send a busy signal back to the caller.
this._returnBusy(callData);
return;
}
}
openChat();
})
} else {
openChat();
}
},
/**

Просмотреть файл

@ -86,12 +86,15 @@ loop.contacts = (function(_, mozL10n) {
return (
React.DOM.ul({className: cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })},
React.DOM.li({className: cx({ "dropdown-menu-item": true }),
onClick: this.onItemClick, 'data-action': "video-call"},
React.DOM.li({className: cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked }),
onClick: this.onItemClick,
'data-action': "video-call"},
React.DOM.i({className: "icon icon-video-call"}),
mozL10n.get("video_call_menu_button")
),
React.DOM.li({className: cx({ "dropdown-menu-item": true }),
React.DOM.li({className: cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked }),
onClick: this.onItemClick, 'data-action': "audio-call"},
React.DOM.i({className: "icon icon-audio-call"}),
mozL10n.get("audio_call_menu_button")
@ -388,10 +391,14 @@ loop.contacts = (function(_, mozL10n) {
});
break;
case "video-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
}
break;
case "audio-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
}
break;
default:
console.error("Unrecognized action: " + actionName);

Просмотреть файл

@ -86,12 +86,15 @@ loop.contacts = (function(_, mozL10n) {
return (
<ul className={cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })}>
<li className={cx({ "dropdown-menu-item": true })}
onClick={this.onItemClick} data-action="video-call">
<li className={cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked })}
onClick={this.onItemClick}
data-action="video-call">
<i className="icon icon-video-call" />
{mozL10n.get("video_call_menu_button")}
</li>
<li className={cx({ "dropdown-menu-item": true })}
<li className={cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked })}
onClick={this.onItemClick} data-action="audio-call">
<i className="icon icon-audio-call" />
{mozL10n.get("audio_call_menu_button")}
@ -388,10 +391,14 @@ loop.contacts = (function(_, mozL10n) {
});
break;
case "video-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
}
break;
case "audio-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
if (!contact.blocked) {
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
}
break;
default:
console.error("Unrecognized action: " + actionName);

Просмотреть файл

@ -406,6 +406,12 @@ loop.store.ConversationStore = (function() {
this._websocket.close();
delete this._websocket;
}
// XXX: The internal callId is different from
// this.get("callId"), see bug 1084228 for more info.
var locationHash = new loop.shared.utils.Helper().locationHash();
var callId = locationHash.match(/\#outgoing\/(.*)/)[1];
navigator.mozLoop.releaseCallData(callId);
},
/**

Просмотреть файл

@ -17,19 +17,25 @@ function promiseImport() {
});
}
const kContactsCount = 7;
add_task(function* test_GoogleImport() {
let stats;
// An error may throw and the test will fail when that happens.
stats = yield promiseImport();
let contactsCount = mockDb.size;
// Assert the world.
Assert.equal(stats.total, 6, "Five contacts should get processed");
Assert.equal(stats.success, 6, "Five contacts should be imported");
Assert.equal(stats.total, contactsCount, "Five contacts should get processed");
Assert.equal(stats.success, contactsCount, "Five contacts should be imported");
yield promiseImport();
Assert.equal(Object.keys(mockDb._store).length, 6, "Database should contain only five contact after reimport");
Assert.equal(Object.keys(mockDb._store).length, contactsCount, "Database should be the same size after reimport");
let c = mockDb._store[mockDb._next_guid - 6];
let currentContact = contactsCount;
let c = mockDb._store[mockDb._next_guid - currentContact];
Assert.equal(c.name[0], "John Smith", "Full name should match");
Assert.equal(c.givenName[0], "John", "Given name should match");
Assert.equal(c.familyName[0], "Smith", "Family name should match");
@ -39,7 +45,7 @@ add_task(function* test_GoogleImport() {
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/0", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 5];
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "Jane Smith", "Full name should match");
Assert.equal(c.givenName[0], "Jane", "Given name should match");
Assert.equal(c.familyName[0], "Smith", "Family name should match");
@ -49,7 +55,7 @@ add_task(function* test_GoogleImport() {
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 4];
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "Davy Randall Jones", "Full name should match");
Assert.equal(c.givenName[0], "Davy Randall", "Given name should match");
Assert.equal(c.familyName[0], "Jones", "Family name should match");
@ -59,7 +65,7 @@ add_task(function* test_GoogleImport() {
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 3];
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "noname@example.com", "Full name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "noname@example.com", "Email should match");
@ -67,7 +73,7 @@ add_task(function* test_GoogleImport() {
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 2];
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "lycnix", "Full name should match");
Assert.equal(c.email[0].type, "other", "Email type should match");
Assert.equal(c.email[0].value, "lycnix", "Email should match");
@ -75,11 +81,19 @@ add_task(function* test_GoogleImport() {
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - 1];
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "+31-6-12345678", "Full name should match");
Assert.equal(c.tel[0].type, "mobile", "Email type should match");
Assert.equal(c.tel[0].value, "+31-6-12345678", "Email should match");
Assert.equal(c.tel[0].type, "mobile", "Phone type should match");
Assert.equal(c.tel[0].value, "+31-6-12345678", "Phone should match");
Assert.equal(c.tel[0].pref, false, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/8", "UID should match and be scoped to provider");
c = mockDb._store[mockDb._next_guid - (--currentContact)];
Assert.equal(c.name[0], "215234523452345", "Full name should match");
Assert.equal(c.tel[0].type, "mobile", "Phone type should match");
Assert.equal(c.tel[0].value, "215234523452345", "Phone should match");
Assert.equal(c.tel[0].pref, false, "Pref should match");
Assert.equal(c.category[0], "google", "Category should match");
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/6", "UID should match and be scoped to provider");
});

Просмотреть файл

@ -16,6 +16,11 @@ const kContacts = [{
"type": ["work"],
"value": "ally@mail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+31-6-12345678"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
@ -27,6 +32,11 @@ const kContacts = [{
"type": ["work"],
"value": "bob@gmail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+1-214-5551234"
}],
category: ["local"],
published: 1406798311748,
updated: 1406798311748
@ -425,11 +435,50 @@ add_task(function* () {
LoopStorage.switchDatabase();
Assert.equal(LoopStorage.databaseName, "default", "The active partition should have changed");
LoopContacts.getAll(function(err, contacts) {
Assert.equal(err, null, "There shouldn't be an error");
for (let i = 0, l = contacts.length; i < l; ++i) {
compareContacts(contacts[i], kContacts[i]);
}
});
contacts = yield LoopContacts.promise("getAll");
for (let i = 0, l = contacts.length; i < l; ++i) {
compareContacts(contacts[i], kContacts[i]);
}
});
// Test searching for contacts.
add_task(function* () {
yield promiseLoadContacts();
let contacts = yield LoopContacts.promise("search", {
q: "bob@gmail.com"
});
Assert.equal(contacts.length, 1, "There should be one contact found");
compareContacts(contacts[0], kContacts[1]);
// Test searching by name.
contacts = yield LoopContacts.promise("search", {
q: "Ally Avocado",
field: "name"
});
Assert.equal(contacts.length, 1, "There should be one contact found");
compareContacts(contacts[0], kContacts[0]);
// Test searching for multiple contacts.
contacts = yield LoopContacts.promise("search", {
q: "google",
field: "category"
});
Assert.equal(contacts.length, 2, "There should be two contacts found");
// Test searching for telephone numbers.
contacts = yield LoopContacts.promise("search", {
q: "+31612345678",
field: "tel"
});
Assert.equal(contacts.length, 1, "There should be one contact found");
compareContacts(contacts[0], kContacts[0]);
// Test searching for telephone numbers without prefixes.
contacts = yield LoopContacts.promise("search", {
q: "5551234",
field: "tel"
});
Assert.equal(contacts.length, 1, "There should be one contact found");
compareContacts(contacts[0], kContacts[1]);
});

Просмотреть файл

@ -102,4 +102,16 @@
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/8" rel="edit" type="application/atom+xml"/>
<gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile" uri="tel:+31-6-12345678">0612345678</gd:phoneNumber>
</entry>
<entry gd:etag="&quot;SX8-ejVSLit7I2A9XRdQFUkDRgY.&quot;">
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/6</id>
<updated>2014-10-17T12:32:08.152Z</updated>
<app:edited xmlns:app="http://www.w3.org/2007/app">2014-10-17T12:32:08.152Z</app:edited>
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
<title/>
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/6" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/6" rel="self" type="application/atom+xml"/>
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/6" rel="edit" type="application/atom+xml"/>
<gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">215234523452345</gd:phoneNumber>
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
</entry>
</feed>

Просмотреть файл

@ -218,6 +218,10 @@ const mockDb = {
_store: { },
_next_guid: 1,
get size() {
return Object.getOwnPropertyNames(this._store).length;
},
add: function(details, callback) {
if (!("id" in details)) {
callback(new Error("No 'id' field present"));

Просмотреть файл

@ -36,6 +36,11 @@ describe("loop.store.ConversationStore", function () {
}]
};
navigator.mozLoop = {
getLoopBoolPref: sandbox.stub(),
releaseCallData: sandbox.stub()
};
dispatcher = new loop.Dispatcher();
client = {
setupOutgoingCall: sinon.stub(),
@ -120,6 +125,8 @@ describe("loop.store.ConversationStore", function () {
describe("#connectionFailure", function() {
beforeEach(function() {
store._websocket = fakeWebsocket;
sandbox.stub(loop.shared.utils.Helper.prototype, "locationHash")
.returns("#outgoing/42");
});
it("should disconnect the session", function() {
@ -145,6 +152,14 @@ describe("loop.store.ConversationStore", function () {
expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
expect(store.get("callStateReason")).eql("fake");
});
it("should release mozLoop callsData", function() {
dispatcher.dispatch(
new sharedActions.ConnectionFailure({reason: "fake"}));
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
});
});
describe("#connectionProgress", function() {
@ -481,6 +496,8 @@ describe("loop.store.ConversationStore", function () {
close: wsCloseSpy
};
store.set({callState: CALL_STATES.ONGOING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationHash")
.returns("#outgoing/42");
});
it("should disconnect the session", function() {
@ -506,6 +523,13 @@ describe("loop.store.ConversationStore", function () {
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
});
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.HangupCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
});
});
describe("#peerHungupCall", function() {
@ -519,6 +543,8 @@ describe("loop.store.ConversationStore", function () {
close: wsCloseSpy
};
store.set({callState: CALL_STATES.ONGOING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationHash")
.returns("#outgoing/42");
});
it("should disconnect the session", function() {
@ -538,6 +564,13 @@ describe("loop.store.ConversationStore", function () {
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
});
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.PeerHungupCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
});
});
describe("#cancelCall", function() {
@ -545,6 +578,8 @@ describe("loop.store.ConversationStore", function () {
store._websocket = fakeWebsocket;
store.set({callState: CALL_STATES.CONNECTING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationHash")
.returns("#outgoing/42");
});
it("should disconnect the session", function() {
@ -579,6 +614,12 @@ describe("loop.store.ConversationStore", function () {
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
});
it("should release mozLoop callsData", function() {
dispatcher.dispatch(new sharedActions.CancelCall());
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "42");
});
});
describe("#retryCall", function() {

Просмотреть файл

@ -1,4 +1,4 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.0.801
Current extension version is: 1.0.907

Просмотреть файл

@ -78,7 +78,9 @@ var DEFAULT_PREFERENCES = {
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
enableWebGL: false,
pdfBugEnabled: false,
disableRange: false,
disableStream: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,

Просмотреть файл

@ -77,7 +77,8 @@ function getFindBar(domWindow) {
var browser = getContainingBrowser(domWindow);
try {
var tabbrowser = browser.getTabBrowser();
var tab = tabbrowser.getTabForBrowser(browser);
var tab;
tab = tabbrowser.getTabForBrowser(browser);
return tabbrowser.getFindBar(tab);
} catch (e) {
// FF22 has no _getTabForBrowser, and FF24 has no getFindBar
@ -162,40 +163,38 @@ function makeContentReadable(obj, window) {
// PDF data storage
function PdfDataListener(length) {
this.length = length; // less than 0, if length is unknown
this.data = new Uint8Array(length >= 0 ? length : 0x10000);
this.buffer = null;
this.loaded = 0;
}
PdfDataListener.prototype = {
append: function PdfDataListener_append(chunk) {
var willBeLoaded = this.loaded + chunk.length;
if (this.length >= 0 && this.length < willBeLoaded) {
// In most of the cases we will pass data as we receive it, but at the
// beginning of the loading we may accumulate some data.
if (!this.buffer) {
this.buffer = new Uint8Array(chunk);
} else {
var buffer = this.buffer;
var newBuffer = new Uint8Array(buffer.length + chunk.length);
newBuffer.set(buffer);
newBuffer.set(chunk, buffer.length);
this.buffer = newBuffer;
}
this.loaded += chunk.length;
if (this.length >= 0 && this.length < this.loaded) {
this.length = -1; // reset the length, server is giving incorrect one
}
if (this.length < 0 && this.data.length < willBeLoaded) {
// data length is unknown and new chunk will not fit in the existing
// buffer, resizing the buffer by doubling the its last length
var newLength = this.data.length;
for (; newLength < willBeLoaded; newLength *= 2) {}
var newData = new Uint8Array(newLength);
newData.set(this.data);
this.data = newData;
}
this.data.set(chunk, this.loaded);
this.loaded = willBeLoaded;
this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0));
},
getData: function PdfDataListener_getData() {
var data = this.data;
if (this.loaded != data.length)
data = data.subarray(0, this.loaded);
delete this.data; // releasing temporary storage
return data;
readData: function PdfDataListener_readData() {
var result = this.buffer;
this.buffer = null;
return result;
},
finish: function PdfDataListener_finish() {
this.isDataReady = true;
if (this.oncompleteCallback) {
this.oncompleteCallback(this.getData());
this.oncompleteCallback(this.readData());
}
},
error: function PdfDataListener_error(errorCode) {
@ -211,7 +210,7 @@ PdfDataListener.prototype = {
set oncomplete(value) {
this.oncompleteCallback = value;
if (this.isDataReady) {
value(this.getData());
value(this.readData());
}
if (this.errorCode) {
value(null, this.errorCode);
@ -234,7 +233,7 @@ function ChromeActions(domWindow, contentDispositionFilename) {
ChromeActions.prototype = {
isInPrivateBrowsing: function() {
return PrivateBrowsingUtils.isWindowPrivate(this.domWindow);
return PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
},
download: function(data, sendResponse) {
var self = this;
@ -329,9 +328,6 @@ ChromeActions.prototype = {
return 'null';
}
},
pdfBugEnabled: function() {
return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
},
supportsIntegratedFind: function() {
// Integrated find is only supported when we're not in a frame
if (this.domWindow.frameElement !== null) {
@ -511,11 +507,13 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
*/
function RangedChromeActions(
domWindow, contentDispositionFilename, originalRequest,
dataListener) {
rangeEnabled, streamingEnabled, dataListener) {
ChromeActions.call(this, domWindow, contentDispositionFilename);
this.dataListener = dataListener;
this.originalRequest = originalRequest;
this.rangeEnabled = rangeEnabled;
this.streamingEnabled = streamingEnabled;
this.pdfUrl = originalRequest.URI.spec;
this.contentLength = originalRequest.contentLength;
@ -534,7 +532,9 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
this.headers[aHeader] = aValue;
}
};
originalRequest.visitRequestHeaders(httpHeaderVisitor);
if (originalRequest.visitRequestHeaders) {
originalRequest.visitRequestHeaders(httpHeaderVisitor);
}
var self = this;
var xhr_onreadystatechange = function xhr_onreadystatechange() {
@ -573,20 +573,46 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
proto.constructor = RangedChromeActions;
proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
this.originalRequest = null;
var self = this;
var data;
if (!this.streamingEnabled) {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
this.originalRequest = null;
data = this.dataListener.readData();
this.dataListener = null;
} else {
data = this.dataListener.readData();
this.dataListener.onprogress = function (loaded, total) {
self.domWindow.postMessage({
pdfjsLoadAction: 'progressiveRead',
loaded: loaded,
total: total,
chunk: self.dataListener.readData()
}, '*');
};
this.dataListener.oncomplete = function () {
delete self.dataListener;
};
}
this.domWindow.postMessage({
pdfjsLoadAction: 'supportsRangedLoading',
rangeEnabled: this.rangeEnabled,
streamingEnabled: this.streamingEnabled,
pdfUrl: this.pdfUrl,
length: this.contentLength,
data: this.dataListener.getData()
data: data
}, '*');
this.dataListener = null;
return true;
};
proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
if (!this.rangeEnabled) {
return;
}
var begin = args.begin;
var end = args.end;
var domWindow = this.domWindow;
@ -828,6 +854,7 @@ PdfStreamConverter.prototype = {
} catch (e) {}
var rangeRequest = false;
var streamRequest = false;
if (isHttpRequest) {
var contentEncoding = 'identity';
try {
@ -840,10 +867,18 @@ PdfStreamConverter.prototype = {
} catch (e) {}
var hash = aRequest.URI.ref;
var isPDFBugEnabled = getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
rangeRequest = contentEncoding === 'identity' &&
acceptRanges === 'bytes' &&
aRequest.contentLength >= 0 &&
hash.indexOf('disableRange=true') < 0;
!getBoolPref(PREF_PREFIX + '.disableRange', false) &&
(!isPDFBugEnabled ||
hash.toLowerCase().indexOf('disablerange=true') < 0);
streamRequest = contentEncoding === 'identity' &&
aRequest.contentLength >= 0 &&
!getBoolPref(PREF_PREFIX + '.disableStream', false) &&
(!isPDFBugEnabled ||
hash.toLowerCase().indexOf('disablestream=true') < 0);
}
aRequest.QueryInterface(Ci.nsIChannel);
@ -897,12 +932,13 @@ PdfStreamConverter.prototype = {
// may have changed during a redirect.
var domWindow = getDOMWindow(channel);
var actions;
if (rangeRequest) {
if (rangeRequest || streamRequest) {
actions = new RangedChromeActions(
domWindow, contentDispositionFilename, aRequest, dataListener);
domWindow, contentDispositionFilename, aRequest,
rangeRequest, streamRequest, dataListener);
} else {
actions = new StandardChromeActions(
domWindow, contentDispositionFilename, dataListener);
domWindow, contentDispositionFilename, dataListener);
}
var requestListener = new RequestListener(actions);
domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {

Просмотреть файл

@ -43,7 +43,9 @@ var DEFAULT_PREFERENCES = {
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
enableWebGL: false,
pdfBugEnabled: false,
disableRange: false,
disableStream: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
@ -285,7 +287,8 @@ let PdfjsChromeUtils = {
*/
function PdfjsFindbarWrapper(aBrowser) {
let tabbrowser = aBrowser.getTabBrowser();
let tab = tabbrowser.getTabForBrowser(aBrowser);
let tab;
tab = tabbrowser.getTabForBrowser(aBrowser);
this._findbar = tabbrowser.getFindBar(tab);
};

Просмотреть файл

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.0.801';
PDFJS.build = 'e77e5c4';
PDFJS.version = '1.0.907';
PDFJS.build = 'e9072ac';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -1362,6 +1362,14 @@ PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc);
PDFJS.disableRange = (PDFJS.disableRange === undefined ?
false : PDFJS.disableRange);
/**
* Disable streaming of PDF file data. By default PDF.js attempts to load PDF
* in chunks. This default behavior can be disabled.
* @var {boolean}
*/
PDFJS.disableStream = (PDFJS.disableStream === undefined ?
false : PDFJS.disableStream);
/**
* Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
* will automatically keep fetching more data even if it isn't needed to display
@ -1560,10 +1568,20 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
/**
* @return {Promise} A promise that is resolved with a lookup table for
* mapping named destinations to reference numbers.
*
* This can be slow for large documents: use getDestination instead
*/
getDestinations: function PDFDocumentProxy_getDestinations() {
return this.transport.getDestinations();
},
/**
* @param {string} id The named destination to get.
* @return {Promise} A promise that is resolved with all information
* of the given named destination.
*/
getDestination: function PDFDocumentProxy_getDestination(id) {
return this.transport.getDestination(id);
},
/**
* @return {Promise} A promise that is resolved with a lookup table for
* mapping named attachments to their content.
@ -2117,6 +2135,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
});
});
pdfDataRangeTransport.addProgressiveReadListener(function(chunk) {
messageHandler.send('OnDataRange', {
chunk: chunk
});
});
messageHandler.on('RequestDataRange',
function transportDataRange(data) {
pdfDataRangeTransport.requestDataRange(data.begin, data.end);
@ -2177,6 +2201,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.downloadInfoCapability.resolve(data);
}, this);
messageHandler.on('PDFManagerReady', function transportPage(data) {
if (this.pdfDataRangeTransport) {
this.pdfDataRangeTransport.transportReady();
}
}, this);
messageHandler.on('StartRenderPage', function transportRender(data) {
var page = this.pageCache[data.pageIndex];
@ -2208,7 +2238,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.commonObjs.resolve(id, error);
break;
} else {
font = new FontFace(exportedData);
font = new FontFaceObject(exportedData);
}
FontLoader.bind(
@ -2321,6 +2351,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
fetchDocument: function WorkerTransport_fetchDocument(source) {
source.disableAutoFetch = PDFJS.disableAutoFetch;
source.disableStream = PDFJS.disableStream;
source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
this.messageHandler.send('GetDocRequest', {
source: source,
@ -2372,6 +2403,10 @@ var WorkerTransport = (function WorkerTransportClosure() {
return this.messageHandler.sendWithPromise('GetDestinations', null);
},
getDestination: function WorkerTransport_getDestination(id) {
return this.messageHandler.sendWithPromise('GetDestination', { id: id } );
},
getAttachments: function WorkerTransport_getAttachments() {
return this.messageHandler.sendWithPromise('GetAttachments', null);
},
@ -5731,8 +5766,8 @@ var FontLoader = {
}
};
var FontFace = (function FontFaceClosure() {
function FontFace(name, file, properties) {
var FontFaceObject = (function FontFaceObjectClosure() {
function FontFaceObject(name, file, properties) {
this.compiledGlyphs = {};
if (arguments.length === 1) {
// importing translated data
@ -5743,8 +5778,9 @@ var FontFace = (function FontFaceClosure() {
return;
}
}
FontFace.prototype = {
bindDOM: function FontFace_bindDOM() {
FontFaceObject.prototype = {
bindDOM: function FontFaceObject_bindDOM() {
if (!this.data) {
return null;
}
@ -5771,7 +5807,7 @@ var FontFace = (function FontFaceClosure() {
return rule;
},
getPathGenerator: function (objs, character) {
getPathGenerator: function FontLoader_getPathGenerator(objs, character) {
if (!(character in this.compiledGlyphs)) {
var js = objs.get(this.loadedName + '_path_' + character);
/*jshint -W054 */
@ -5780,7 +5816,7 @@ var FontFace = (function FontFaceClosure() {
return this.compiledGlyphs[character];
}
};
return FontFace;
return FontFaceObject;
})();

Просмотреть файл

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.0.801';
PDFJS.build = 'e77e5c4';
PDFJS.version = '1.0.907';
PDFJS.build = 'e9072ac';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -1309,7 +1309,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
this.numChunksLoaded = 0;
this.numChunks = Math.ceil(length / chunkSize);
this.manager = manager;
this.initialDataLength = 0;
this.progressiveDataLength = 0;
this.lastSuccessfulEnsureByteChunk = -1; // a single-entry cache
}
@ -1320,7 +1320,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
getMissingChunks: function ChunkedStream_getMissingChunks() {
var chunks = [];
for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
chunks.push(chunk);
}
}
@ -1352,21 +1352,29 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var curChunk;
for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
if (!(curChunk in this.loadedChunks)) {
if (!this.loadedChunks[curChunk]) {
this.loadedChunks[curChunk] = true;
++this.numChunksLoaded;
}
}
},
onReceiveInitialData: function ChunkedStream_onReceiveInitialData(data) {
this.bytes.set(data);
this.initialDataLength = data.length;
var endChunk = (this.end === data.length ?
this.numChunks : Math.floor(data.length / this.chunkSize));
for (var i = 0; i < endChunk; i++) {
this.loadedChunks[i] = true;
++this.numChunksLoaded;
onReceiveProgressiveData:
function ChunkedStream_onReceiveProgressiveData(data) {
var position = this.progressiveDataLength;
var beginChunk = Math.floor(position / this.chunkSize);
this.bytes.set(new Uint8Array(data), position);
position += data.byteLength;
this.progressiveDataLength = position;
var endChunk = position >= this.end ? this.numChunks :
Math.floor(position / this.chunkSize);
var curChunk;
for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
if (!this.loadedChunks[curChunk]) {
this.loadedChunks[curChunk] = true;
++this.numChunksLoaded;
}
}
},
@ -1376,7 +1384,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
return;
}
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
throw new MissingDataException(pos, pos + 1);
}
this.lastSuccessfulEnsureByteChunk = chunk;
@ -1387,7 +1395,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
return;
}
if (end <= this.initialDataLength) {
if (end <= this.progressiveDataLength) {
return;
}
@ -1395,7 +1403,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var beginChunk = Math.floor(begin / chunkSize);
var endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
throw new MissingDataException(begin, end);
}
}
@ -1404,13 +1412,13 @@ var ChunkedStream = (function ChunkedStreamClosure() {
nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
var chunk, n;
for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
return chunk;
}
}
// Wrap around to beginning
for (chunk = 0; chunk < beginChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
return chunk;
}
}
@ -1418,7 +1426,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
},
hasChunk: function ChunkedStream_hasChunk(chunk) {
return chunk in this.loadedChunks;
return !!this.loadedChunks[chunk];
},
get length() {
@ -1517,7 +1525,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
var missingChunks = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
if (!this.loadedChunks[chunk]) {
missingChunks.push(chunk);
}
}
@ -1575,28 +1583,16 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
this.chunksNeededByRequest = {};
this.requestsByChunk = {};
this.callbacksByRequest = {};
this.progressiveDataLength = 0;
this._loadedStreamCapability = createPromiseCapability();
if (args.initialData) {
this.setInitialData(args.initialData);
this.onReceiveData({chunk: args.initialData});
}
}
ChunkedStreamManager.prototype = {
setInitialData: function ChunkedStreamManager_setInitialData(data) {
this.stream.onReceiveInitialData(data);
if (this.stream.allChunksLoaded()) {
this._loadedStreamCapability.resolve(this.stream);
} else if (this.msgHandler) {
this.msgHandler.send('DocProgress', {
loaded: data.length,
total: this.length
});
}
},
onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
return this._loadedStreamCapability.promise;
},
@ -1734,13 +1730,21 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
var chunk = args.chunk;
var begin = args.begin;
var isProgressive = args.begin === undefined;
var begin = isProgressive ? this.progressiveDataLength : args.begin;
var end = begin + chunk.byteLength;
var beginChunk = this.getBeginChunk(begin);
var endChunk = this.getEndChunk(end);
var beginChunk = Math.floor(begin / this.chunkSize);
var endChunk = end < this.length ? Math.floor(end / this.chunkSize) :
Math.ceil(end / this.chunkSize);
if (isProgressive) {
this.stream.onReceiveProgressiveData(chunk);
this.progressiveDataLength = end;
} else {
this.stream.onReceiveData(begin, chunk);
}
this.stream.onReceiveData(begin, chunk);
if (this.stream.allChunksLoaded()) {
this._loadedStreamCapability.resolve(this.stream);
}
@ -1877,6 +1881,10 @@ var BasePdfManager = (function BasePdfManagerClosure() {
return new NotImplementedException();
},
sendProgressiveData: function BasePdfManager_sendProgressiveData(chunk) {
return new NotImplementedException();
},
updatePassword: function BasePdfManager_updatePassword(password) {
this.pdfDocument.xref.password = this.password = password;
if (this._passwordChangedCapability) {
@ -2013,6 +2021,11 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
this.streamManager.requestAllChunks();
};
NetworkPdfManager.prototype.sendProgressiveData =
function NetworkPdfManager_sendProgressiveData(chunk) {
this.streamManager.onReceiveData({ chunk: chunk });
};
NetworkPdfManager.prototype.onLoadedStream =
function NetworkPdfManager_getLoadedStream() {
return this.streamManager.onLoadedStream();
@ -2959,6 +2972,38 @@ var Catalog = (function CatalogClosure() {
}
return shadow(this, 'destinations', dests);
},
getDestination: function Catalog_getDestination(destinationId) {
function fetchDestination(dest) {
return isDict(dest) ? dest.get('D') : dest;
}
var xref = this.xref;
var dest, nameTreeRef, nameDictionaryRef;
var obj = this.catDict.get('Names');
if (obj && obj.has('Dests')) {
nameTreeRef = obj.getRaw('Dests');
} else if (this.catDict.has('Dests')) {
nameDictionaryRef = this.catDict.get('Dests');
}
if (nameDictionaryRef) {
// reading simple destination dictionary
obj = nameDictionaryRef;
obj.forEach(function catalogForEach(key, value) {
if (!value) {
return;
}
if (key === destinationId) {
dest = fetchDestination(value);
}
});
}
if (nameTreeRef) {
var nameTree = new NameTree(nameTreeRef, xref);
dest = fetchDestination(nameTree.get(destinationId));
}
return dest;
},
get attachments() {
var xref = this.xref;
var attachments = null, nameTreeRef;
@ -3860,6 +3905,76 @@ var NameTree = (function NameTreeClosure() {
}
}
return dict;
},
get: function NameTree_get(destinationId) {
if (!this.root) {
return null;
}
var xref = this.xref;
var kidsOrNames = xref.fetchIfRef(this.root);
var loopCount = 0;
var MAX_NAMES_LEVELS = 10;
var l, r, m;
// Perform a binary search to quickly find the entry that
// contains the named destination we are looking for.
while (kidsOrNames.has('Kids')) {
loopCount++;
if (loopCount > MAX_NAMES_LEVELS) {
warn('Search depth limit for named destionations has been reached.');
return null;
}
var kids = kidsOrNames.get('Kids');
if (!isArray(kids)) {
return null;
}
l = 0;
r = kids.length - 1;
while (l <= r) {
m = (l + r) >> 1;
var kid = xref.fetchIfRef(kids[m]);
var limits = kid.get('Limits');
if (destinationId < limits[0]) {
r = m - 1;
} else if (destinationId > limits[1]) {
l = m + 1;
} else {
kidsOrNames = xref.fetchIfRef(kids[m]);
break;
}
}
if (l > r) {
return null;
}
}
// If we get here, then we have found the right entry. Now
// go through the named destinations in the Named dictionary
// until we find the exact destination we're looking for.
var names = kidsOrNames.get('Names');
if (isArray(names)) {
// Perform a binary search to reduce the lookup time.
l = 0;
r = names.length - 2;
while (l <= r) {
// Check only even indices (0, 2, 4, ...) because the
// odd indices contain the actual D array.
m = (l + r) & ~1;
if (destinationId < names[m]) {
r = m - 2;
} else if (destinationId > names[m]) {
l = m + 2;
} else {
return xref.fetchIfRef(names[m + 1]);
}
}
}
return null;
}
};
return NameTree;
@ -15857,6 +15972,12 @@ var Font = (function FontClosure() {
for (var code in GlyphMapForStandardFonts) {
map[+code] = GlyphMapForStandardFonts[code];
}
var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
if (!isIdentityUnicode) {
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
this.toFontChar = map;
this.toUnicode = new ToUnicodeMap(map);
} else if (/Symbol/i.test(fontName)) {
@ -15868,6 +15989,13 @@ var Font = (function FontClosure() {
}
this.toFontChar[charCode] = fontChar;
}
for (charCode in properties.differences) {
fontChar = GlyphsUnicode[properties.differences[charCode]];
if (!fontChar) {
continue;
}
this.toFontChar[charCode] = fontChar;
}
} else if (/Dingbats/i.test(fontName)) {
var dingbats = Encodings.ZapfDingbatsEncoding;
for (charCode in dingbats) {
@ -15928,6 +16056,9 @@ var Font = (function FontClosure() {
var data;
switch (type) {
case 'MMType1':
info('MMType1 font (' + name + '), falling back to Type1.');
/* falls through */
case 'Type1':
case 'CIDFontType0':
this.mimetype = 'font/opentype';
@ -31206,7 +31337,7 @@ var JpegStream = (function JpegStreamClosure() {
var jpegImage = new JpegImage();
// checking if values needs to be transformed before conversion
if (this.dict && isArray(this.dict.get('Decode'))) {
if (this.forceRGB && this.dict && isArray(this.dict.get('Decode'))) {
var decodeArr = this.dict.get('Decode');
var bitsPerComponent = this.dict.get('BitsPerComponent') || 8;
var decodeArrLength = decodeArr.length;
@ -32840,6 +32971,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
httpHeaders: source.httpHeaders,
withCredentials: source.withCredentials
});
var cachedChunks = [];
var fullRequestXhrId = networkManager.requestFull({
onHeadersReceived: function onHeadersReceived() {
if (disableRange) {
@ -32870,11 +33002,18 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
return;
}
// NOTE: by cancelling the full request, and then issuing range
// requests, there will be an issue for sites where you can only
// request the pdf once. However, if this is the case, then the
// server should not be returning that it can support range requests.
networkManager.abortRequest(fullRequestXhrId);
if (networkManager.isStreamingRequest(fullRequestXhrId)) {
// We can continue fetching when progressive loading is enabled,
// and we don't need the autoFetch feature.
source.disableAutoFetch = true;
} else {
// NOTE: by cancelling the full request, and then issuing range
// requests, there will be an issue for sites where you can only
// request the pdf once. However, if this is the case, then the
// server should not be returning that it can support range
// requests.
networkManager.abortRequest(fullRequestXhrId);
}
try {
pdfManager = new NetworkPdfManager(source, handler);
@ -32884,10 +33023,44 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}
},
onProgressiveData: source.disableStream ? null :
function onProgressiveData(chunk) {
if (!pdfManager) {
cachedChunks.push(chunk);
return;
}
pdfManager.sendProgressiveData(chunk);
},
onDone: function onDone(args) {
if (pdfManager) {
return; // already processed
}
var pdfFile;
if (args === null) {
// TODO add some streaming manager, e.g. for unknown length files.
// The data was returned in the onProgressiveData, combining...
var pdfFileLength = 0, pos = 0;
cachedChunks.forEach(function (chunk) {
pdfFileLength += chunk.byteLength;
});
if (source.length && pdfFileLength !== source.length) {
warn('reported HTTP length is different from actual');
}
var pdfFileArray = new Uint8Array(pdfFileLength);
cachedChunks.forEach(function (chunk) {
pdfFileArray.set(new Uint8Array(chunk), pos);
pos += chunk.byteLength;
});
pdfFile = pdfFileArray.buffer;
} else {
pdfFile = args.chunk;
}
// the data is array, instantiating directly from it
try {
pdfManager = new LocalPdfManager(args.chunk, source.password);
pdfManager = new LocalPdfManager(pdfFile, source.password);
pdfManagerCapability.resolve();
} catch (ex) {
pdfManagerCapability.reject(ex);
@ -32982,6 +33155,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
PDFJS.cMapPacked = data.cMapPacked === true;
getPdfManager(data).then(function () {
handler.send('PDFManagerReady', null);
pdfManager.onLoadedStream().then(function(stream) {
handler.send('DataLoaded', { length: stream.bytes.byteLength });
});
@ -33036,6 +33210,12 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}
);
handler.on('GetDestination',
function wphSetupGetDestination(data) {
return pdfManager.ensureCatalog('getDestination', [ data.id ]);
}
);
handler.on('GetAttachments',
function wphSetupGetAttachments(data) {
return pdfManager.ensureCatalog('attachments');

Просмотреть файл

@ -62,11 +62,11 @@ var NetworkManager = (function NetworkManagerClosure() {
return data;
}
var length = data.length;
var buffer = new Uint8Array(length);
var array = new Uint8Array(length);
for (var i = 0; i < length; i++) {
buffer[i] = data.charCodeAt(i) & 0xFF;
array[i] = data.charCodeAt(i) & 0xFF;
}
return buffer;
return array.buffer;
}
NetworkManager.prototype = {
@ -81,11 +81,11 @@ var NetworkManager = (function NetworkManagerClosure() {
return this.request(args);
},
requestFull: function NetworkManager_requestRange(listeners) {
requestFull: function NetworkManager_requestFull(listeners) {
return this.request(listeners);
},
request: function NetworkManager_requestRange(args) {
request: function NetworkManager_request(args) {
var xhr = this.getXhr();
var xhrId = this.currXhrId++;
var pendingRequest = this.pendingRequests[xhrId] = {
@ -109,27 +109,54 @@ var NetworkManager = (function NetworkManagerClosure() {
pendingRequest.expectedStatus = 200;
}
xhr.responseType = 'arraybuffer';
if (args.onProgress) {
xhr.onprogress = args.onProgress;
if (args.onProgressiveData) {
xhr.responseType = 'moz-chunked-arraybuffer';
if (xhr.responseType === 'moz-chunked-arraybuffer') {
pendingRequest.onProgressiveData = args.onProgressiveData;
pendingRequest.mozChunked = true;
} else {
xhr.responseType = 'arraybuffer';
}
} else {
xhr.responseType = 'arraybuffer';
}
if (args.onError) {
xhr.onerror = function(evt) {
args.onError(xhr.status);
};
}
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
xhr.onprogress = this.onProgress.bind(this, xhrId);
pendingRequest.onHeadersReceived = args.onHeadersReceived;
pendingRequest.onDone = args.onDone;
pendingRequest.onError = args.onError;
pendingRequest.onProgress = args.onProgress;
xhr.send(null);
return xhrId;
},
onProgress: function NetworkManager_onProgress(xhrId, evt) {
var pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) {
// Maybe abortRequest was called...
return;
}
if (pendingRequest.mozChunked) {
var chunk = getArrayBuffer(pendingRequest.xhr);
pendingRequest.onProgressiveData(chunk);
}
var onProgress = pendingRequest.onProgress;
if (onProgress) {
onProgress(evt);
}
},
onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
var pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) {
@ -190,6 +217,8 @@ var NetworkManager = (function NetworkManagerClosure() {
begin: begin,
chunk: chunk
});
} else if (pendingRequest.onProgressiveData) {
pendingRequest.onDone(null);
} else {
pendingRequest.onDone({
begin: 0,
@ -209,6 +238,10 @@ var NetworkManager = (function NetworkManagerClosure() {
return this.pendingRequests[xhrId].xhr;
},
isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
return !!(this.pendingRequests[xhrId].onProgressiveData);
},
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
return xhrId in this.pendingRequests;
},

Просмотреть файл

@ -15,7 +15,7 @@
* limitations under the License.
*/
/* jshint esnext:true */
/* globals Components, PdfjsContentUtils, PdfJs */
/* globals Components, PdfjsContentUtils, PdfJs, Services */
'use strict';

Просмотреть файл

@ -112,13 +112,20 @@ var FontInspector = (function FontInspectorClosure() {
return moreInfo;
}
var moreInfo = properties(fontObj, ['name', 'type']);
var m = /url\(['"]?([^\)"']+)/.exec(url);
var fontName = fontObj.loadedName;
var font = document.createElement('div');
var name = document.createElement('span');
name.textContent = fontName;
var download = document.createElement('a');
download.href = m[1];
if (url) {
url = /url\(['"]?([^\)"']+)/.exec(url);
download.href = url[1];
} else if (fontObj.data) {
url = URL.createObjectURL(new Blob([fontObj.data], {
type: fontObj.mimeType
}));
}
download.href = url;
download.textContent = 'Download';
var logIt = document.createElement('a');
logIt.href = '';
@ -211,6 +218,7 @@ var StepperManager = (function StepperManagerClosure() {
},
selectStepper: function selectStepper(pageIndex, selectPanel) {
var i;
pageIndex = pageIndex | 0;
if (selectPanel) {
this.manager.selectPanel(this);
}
@ -419,7 +427,7 @@ var Stepper = (function StepperClosure() {
var allRows = this.panel.getElementsByClassName('line');
for (var x = 0, xx = allRows.length; x < xx; ++x) {
var row = allRows[x];
if (parseInt(row.dataset.idx, 10) === idx) {
if ((row.dataset.idx | 0) === idx) {
row.style.backgroundColor = 'rgb(251,250,207)';
row.scrollIntoView();
} else {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.5 KiB

После

Ширина:  |  Высота:  |  Размер: 7.2 KiB

Двоичные данные
browser/extensions/pdfjs/content/web/images/loading-small@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

@ -1,4 +1,4 @@
/* Copyright 2012 Mozilla Foundation
/* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,6 +13,141 @@
* limitations under the License.
*/
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.textLayer > div {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-moz-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
.textLayer .highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
}
.textLayer .highlight.begin {
border-radius: 4px 0px 0px 4px;
}
.textLayer .highlight.end {
border-radius: 0px 4px 4px 0px;
}
.textLayer .highlight.middle {
border-radius: 0px;
}
.textLayer .highlight.selected {
background-color: rgb(0, 100, 0);
}
.pdfViewer .canvasWrapper {
overflow: hidden;
}
.pdfViewer .page {
direction: ltr;
width: 816px;
height: 1056px;
margin: 1px auto -8px auto;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
border-image: url(images/shadow.png) 9 9 repeat;
background-color: white;
}
.pdfViewer .page canvas {
margin: 0;
display: block;
}
.pdfViewer .page .loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: url('images/loading-icon.gif') center no-repeat;
}
.pdfViewer .page .annotLink > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
}
:-moz-full-screen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
:fullscreen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
.pdfViewer .page .annotationHighlight {
position: absolute;
border: 2px #FFFF99 solid;
}
.pdfViewer .page .annotText > img {
position: absolute;
cursor: pointer;
}
.pdfViewer .page .annotTextContentWrapper {
position: absolute;
width: 20em;
}
.pdfViewer .page .annotTextContent {
z-index: 200;
float: left;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 5px #333;
border-radius: 2px;
padding: 0.6em;
cursor: pointer;
}
.pdfViewer .page .annotTextContent > h1 {
font-size: 1em;
border-bottom: 1px solid #000000;
padding-bottom: 0.2em;
}
.pdfViewer .page .annotTextContent > p {
padding-top: 0.2em;
}
.pdfViewer .page .annotLink > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
* {
padding: 0;
margin: 0;
@ -65,16 +200,6 @@ select {
cursor: none;
}
:-moz-full-screen .page {
margin-bottom: 100%;
border: 0;
}
:fullscreen .page {
margin-bottom: 100%;
border: 0;
}
:-moz-full-screen a:not(.internalLink) {
display: none;
}
@ -984,6 +1109,12 @@ html[dir='rtl'] .verticalToolbarSeparator {
width: 40px;
}
.toolbarField.pageNumber.visiblePageIsLoading {
background-image: url(images/loading-small.png);
background-repeat: no-repeat;
background-position: 1px;
}
.toolbarField:hover {
background-color: hsla(0,0%,100%,.11);
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
@ -1167,135 +1298,17 @@ html[dir='rtl'] .attachmentsItem > button {
cursor: default;
}
.canvasWrapper {
overflow: hidden;
}
canvas {
margin: 0;
display: block;
}
.page {
direction: ltr;
width: 816px;
height: 1056px;
margin: 1px auto -8px auto;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
border-image: url(images/shadow.png) 9 9 repeat;
background-color: white;
}
.annotLink > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
}
.loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: url('images/loading-icon.gif') center no-repeat;
}
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.textLayer > div {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-moz-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
.textLayer .highlight {
margin: -1px;
padding: 1px;
background-color: rgba(180, 0, 170, 0.2);
border-radius: 4px;
}
.textLayer .highlight.begin {
border-radius: 4px 0px 0px 4px;
}
.textLayer .highlight.end {
border-radius: 0px 4px 4px 0px;
}
.textLayer .highlight.middle {
border-radius: 0px;
}
.textLayer .highlight.selected {
background-color: rgba(0, 100, 0, 0.2);
}
/* TODO: file FF bug to support ::-moz-selection:window-inactive
so we can override the opaque grey background when the window is inactive;
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
::selection { background:rgba(0,0,255,0.3); }
::-moz-selection { background:rgba(0,0,255,0.3); }
::selection { background: rgba(0,0,255,0.3); }
::-moz-selection { background: rgba(0,0,255,0.3); }
.annotationHighlight {
position: absolute;
border: 2px #FFFF99 solid;
}
.annotText > img {
position: absolute;
cursor: pointer;
}
.annotTextContentWrapper {
position: absolute;
width: 20em;
}
.annotTextContent {
z-index: 200;
float: left;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 5px #333;
border-radius: 2px;
padding: 0.6em;
cursor: pointer;
}
.annotTextContent > h1 {
font-size: 1em;
border-bottom: 1px solid #000000;
padding-bottom: 0.2em;
}
.annotTextContent > p {
padding-top: 0.2em;
}
.annotLink > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
.textLayer ::selection { background: rgb(0,0,255); }
.textLayer ::-moz-selection { background: rgb(0,0,255); }
.textLayer {
opacity: 0.2;
}
#errorWrapper {
@ -1477,11 +1490,9 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
.debuggerShowText {
background: none repeat scroll 0 0 yellow;
color: blue;
opacity: 0.3;
}
.debuggerHideText:hover {
background: none repeat scroll 0 0 yellow;
opacity: 0.3;
}
#PDFBug .stats {
font-family: courier;
@ -1561,6 +1572,12 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
left: 186px;
}
.toolbarField.pageNumber.visiblePageIsLoading,
#findInput[data-status="pending"] {
background-image: url(images/loading-small@2x.png);
background-size: 16px 17px;
}
.dropdownToolbarButton {
background: url(images/toolbarButton-menuArrows@2x.png) no-repeat;
background-size: 7px 16px;
@ -1710,6 +1727,8 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
display: none;
border: none;
box-shadow: none;
background-clip: content-box;
background-color: white;
}
.page[data-loaded] {
@ -1731,6 +1750,7 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
position: relative;
top: 0;
left: 0;
display: block;
}
}

Просмотреть файл

@ -245,7 +245,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/
</menu>
<div id="viewerContainer" tabindex="0">
<div id="viewer"></div>
<div id="viewer" class="pdfViewer"></div>
</div>
<div id="errorWrapper" hidden='true'>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -97,7 +97,7 @@ quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
# %2$S is the keyboard shortcut
quit-button.tooltiptext.mac = Quit %1$S (%2$S)
# LOCALIZATION NOTE(loop-call-button2.label2): This is a brand name, request
# LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
# approval before you change it.
loop-call-button3.label = Hello
loop-call-button2.tooltiptext = Start a conversation

Просмотреть файл

@ -2068,15 +2068,6 @@ toolbarbutton.chevron > .toolbarbutton-icon {
/* Social toolbar item */
#social-provider-button {
-moz-image-region: rect(0, 16px, 16px, 0);
list-style-image: url(chrome://browser/skin/social/services-16.png);
}
#social-provider-button > .toolbarbutton-menu-dropmarker {
display: none;
}
.popup-notification-icon[popupid="servicesInstall"] {
list-style-image: url(chrome://browser/skin/social/services-64.png);
}

Просмотреть файл

@ -4366,64 +4366,6 @@ menulist.translate-infobar-element > .menulist-dropmarker {
border-radius: 1px;
}
/* === social toolbar button === */
#social-toolbar-item > .toolbarbutton-1 {
margin-left: 0;
margin-right: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr) {
-moz-border-end-width: 0;
}
#social-toolbar-item > .toolbarbutton-1:last-child:-moz-locale-dir(ltr) {
-moz-border-end-width: 1px;
}
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl) {
-moz-border-start-width: 0;
}
#social-toolbar-item > .toolbarbutton-1:first-child:-moz-locale-dir(rtl) {
-moz-border-start-width: 1px;
}
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr):first-child,
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl):last-child {
margin-left: 4px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl):first-child,
#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr):last-child {
margin-right: 4px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
#social-toolbar-item > toolbaritem {
margin: 0;
}
#social-provider-button {
list-style-image: url(chrome://browser/skin/social/services-16.png);
}
@media (min-resolution: 2dppx) {
#social-provider-button {
list-style-image: url(chrome://browser/skin/social/services-16@2x.png);
}
#social-provider-button > .toolbarbutton-icon {
width: 16px;
}
}
#social-provider-button > .toolbarbutton-menu-dropmarker {
display: none;
}
.popup-notification-icon[popupid="servicesInstall"] {
list-style-image: url(chrome://browser/skin/social/services-64.png);
}

Просмотреть файл

@ -623,7 +623,7 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-padding-end: 5px;
}
#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#social-provider-button):not(#PanelUI-menu-button) {
#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) {
padding-left: 5px;
padding-right: 5px;
}
@ -698,7 +698,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
width: 32px;
}
#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#social-provider-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
-moz-padding-end: 17px;
}
@ -2677,15 +2677,6 @@ notification[value="translation"] {
/* Social toolbar item */
#social-provider-button {
-moz-image-region: rect(0, 16px, 16px, 0);
list-style-image: url(chrome://browser/skin/social/services-16.png);
}
#social-provider-button > .toolbarbutton-menu-dropmarker {
display: none;
}
#switch-to-metro-button[cui-areatype="toolbar"] {
list-style-image: url(chrome://browser/skin/Metro_Glyph.png);
}

Просмотреть файл

@ -1788,7 +1788,7 @@ public class BrowserApp extends GeckoApp
mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
mBrowserToolbar.triggerTabsPanelTransition(mMainLayoutAnimator, areTabsShown());
// If the tabs layout is animating onto the screen, pin the dynamic
// If the tabs panel is animating onto the screen, pin the dynamic
// toolbar.
if (mDynamicToolbar.isEnabled()) {
if (width > 0 && height > 0) {
@ -2376,7 +2376,7 @@ public class BrowserApp extends GeckoApp
}
/**
* Hides certain UI elements (e.g. button toast, tabs tray) when the
* Hides certain UI elements (e.g. button toast, tabs panel) when the
* user touches the main layout.
*/
private class HideOnTouchListener implements TouchEventInterceptor {
@ -2412,7 +2412,7 @@ public class BrowserApp extends GeckoApp
return false;
}
// If the tab tray is showing, hide the tab tray and don't send the event to content.
// If the tabs panel is showing, hide the tab panel and don't send the event to content.
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
mIsHidingTabs = true;
return true;

Просмотреть файл

@ -128,7 +128,7 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
lastModifiedView.setText(TabsAccessor.getLastSyncedString(context, now, client.lastModified));
// These views exists only in some of our group views: they are present
// for the home panel groups and not for the tabs tray groups.
// for the home panel groups and not for the tabs panel groups.
// Therefore, we must handle null.
final ImageView deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
if (deviceTypeView != null) {
@ -174,7 +174,7 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
final RemoteTab tab = client.tabs.get(childPosition);
// The view is a TwoLinePageRow only for some of our child views: it's
// present for the home panel children and not for the tabs tray
// present for the home panel children and not for the tabs panel
// children. Therefore, we must handle one case manually.
if (view instanceof TwoLinePageRow) {
((TwoLinePageRow) view).update(tab.title, tab.url);

Просмотреть файл

@ -33,7 +33,7 @@
</RelativeLayout>
<view class="org.mozilla.gecko.tabs.TabsPanel$PanelViewContainer"
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayoutContainer"
android:id="@+id/tabs_container"
android:layout_width="match_parent"
android:layout_height="0dip"
@ -41,7 +41,7 @@
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
android:id="@+id/normal_tabs"
style="@style/TabsList"
style="@style/TabsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"

Просмотреть файл

@ -26,7 +26,7 @@
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:paddingTop="4dip"
style="@style/TabRowTextAppearance"
style="@style/TabLayoutItemTextAppearance"
android:textSize="12sp"
android:textColor="#FFFFFFFF"
android:singleLine="true"

Просмотреть файл

@ -45,11 +45,11 @@
</ScrollView>
<!-- Note: for an unknown reason, scrolling in the TabsListLayout
<!-- Note: for an unknown reason, scrolling in the TabsLayout
does not work unless it is laid out after the empty view. -->
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
android:id="@+id/private_tabs_tray"
style="@style/TabsList"
android:id="@+id/private_tabs_layout"
style="@style/TabsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"

Просмотреть файл

@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:padding="4dip"
style="@style/TabRowTextAppearance"
style="@style/TabLayoutItemTextAppearance"
android:textSize="12sp"
android:textColor="#FF222222"
android:singleLine="true"

Просмотреть файл

@ -36,7 +36,7 @@
android:paddingTop="4dip"
android:paddingLeft="8dip"
android:paddingRight="4dip"
style="@style/TabRowTextAppearance"
style="@style/TabLayoutItemTextAppearance"
android:textColor="#FFFFFFFF"
android:textSize="14sp"
android:singleLine="false"

Просмотреть файл

@ -50,14 +50,14 @@
</RelativeLayout>
<view class="org.mozilla.gecko.tabs.TabsPanel$PanelViewContainer"
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayoutContainer"
android:id="@+id/tabs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
android:id="@+id/normal_tabs"
style="@style/TabsList"
style="@style/TabsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"

Просмотреть файл

@ -5,7 +5,7 @@
<resources>
<style name="TabsList" parent="TabsListBase">
<style name="TabsLayout" parent="TabsLayoutBase">
<item name="android:orientation">horizontal</item>
<item name="android:scrollbars">horizontal</item>
</style>

Просмотреть файл

@ -5,7 +5,7 @@
<resources>
<style name="TabsList" parent="TabsListBase">
<style name="TabsLayout" parent="TabsLayoutBase">
<item name="android:orientation">vertical</item>
<item name="android:scrollbars">vertical</item>
</style>

Просмотреть файл

@ -50,7 +50,7 @@
<item name="android:orientation">horizontal</item>
</style>
<style name="TabsList" parent="TabsListBase">
<style name="TabsLayout" parent="TabsLayoutBase">
<item name="android:orientation">horizontal</item>
<item name="android:scrollbars">horizontal</item>
</style>

Просмотреть файл

@ -51,8 +51,8 @@
<item name="android:displayOptions">showHome|homeAsUp|showTitle</item>
</style>
<!-- TabsTray ActionBar -->
<style name="ActionBar.TabsTray">
<!-- TabsLayout ActionBar -->
<style name="ActionBar.TabsLayout">
<item name="android:visibility">gone</item>
</style>

Просмотреть файл

@ -98,7 +98,7 @@
<attr name="entryKeys" format="string"/>
</declare-styleable>
<declare-styleable name="TabsTray">
<declare-styleable name="TabsLayout">
<attr name="tabs">
<flag name="tabs_normal" value="0x00" />
<flag name="tabs_private" value ="0x01" />

Просмотреть файл

@ -109,7 +109,7 @@
<dimen name="tabs_strip_button_width">100dp</dimen>
<dimen name="tabs_strip_button_padding">18dp</dimen>
<dimen name="tabs_strip_shadow_size">1dp</dimen>
<dimen name="tabs_tray_horizontal_height">156dp</dimen>
<dimen name="tabs_layout_horizontal_height">156dp</dimen>
<dimen name="text_selection_handle_width">47dp</dimen>
<dimen name="text_selection_handle_height">58dp</dimen>
<dimen name="text_selection_handle_shadow">11dp</dimen>

Просмотреть файл

@ -480,13 +480,13 @@
<item name="android:layout_weight">0.0</item>
</style>
<!-- TabsTray List -->
<style name="TabsListBase">
<!-- TabsLayout -->
<style name="TabsLayoutBase">
<item name="android:background">@android:color/transparent</item>
<item name="android:listSelector">@android:color/transparent</item>
</style>
<style name="TabsList" parent="TabsListBase">
<style name="TabsLayout" parent="TabsLayoutBase">
<item name="android:orientation">vertical</item>
<item name="android:scrollbars">vertical</item>
</style>
@ -615,15 +615,15 @@
<item name="android:drawSelectorOnTop">true</item>
</style>
<!-- TabsTray Row -->
<style name="TabRowTextAppearance">
<!-- TabsLayout Row -->
<style name="TabLayoutItemTextAppearance">
<item name="android:textColor">#FFFFFFFF</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">middle</item>
</style>
<!-- TabsTray RemoteTabs Row Url -->
<style name="TabRowTextAppearance.Url">
<!-- TabsLayout RemoteTabs Row Url -->
<style name="TabLayoutItemTextAppearance.Url">
<item name="android:textColor">#FFA4A7A9</item>
</style>

Просмотреть файл

@ -33,7 +33,7 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_tray);
tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_layout);
final View emptyView = findViewById(R.id.private_tabs_empty);
tabsLayout.setEmptyView(emptyView);

Просмотреть файл

@ -47,8 +47,8 @@ class TabsGridLayout extends GridView
super(context, attrs, R.attr.tabGridLayoutViewStyle);
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
mIsPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
a.recycle();
mTabsAdapter = new TabsGridLayoutAdapter(mContext);

Просмотреть файл

@ -65,8 +65,8 @@ class TabsListLayout extends TwoWayView
setItemsCanFocus(true);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
mIsPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
a.recycle();
mTabsAdapter = new TabsListLayoutAdapter(mContext);

Просмотреть файл

@ -83,7 +83,7 @@ public class TabsPanel extends LinearLayout
private final GeckoApp mActivity;
private final LightweightTheme mTheme;
private RelativeLayout mHeader;
private PanelViewContainer mPanelsContainer;
private TabsLayoutContainer mTabsContainer;
private PanelView mPanel;
private PanelView mPanelNormal;
private PanelView mPanelPrivate;
@ -152,7 +152,7 @@ public class TabsPanel extends LinearLayout
private void initialize() {
mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header);
mPanelsContainer = (PanelViewContainer) findViewById(R.id.tabs_container);
mTabsContainer = (TabsLayoutContainer) findViewById(R.id.tabs_container);
mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
mPanelNormal.setTabsPanel(this);
@ -279,19 +279,19 @@ public class TabsPanel extends LinearLayout
return mActivity.onOptionsItemSelected(item);
}
private static int getPanelsContainerHeight(PanelViewContainer panelsContainer) {
Resources resources = panelsContainer.getContext().getResources();
private static int getTabContainerHeight(TabsLayoutContainer tabsContainer) {
Resources resources = tabsContainer.getContext().getResources();
PanelView panelView = panelsContainer.getCurrentPanelView();
PanelView panelView = tabsContainer.getCurrentPanelView();
if (panelView != null && !panelView.shouldExpand()) {
return resources.getDimensionPixelSize(R.dimen.tabs_tray_horizontal_height);
return resources.getDimensionPixelSize(R.dimen.tabs_layout_horizontal_height);
}
int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
int screenHeight = resources.getDisplayMetrics().heightPixels;
Rect windowRect = new Rect();
panelsContainer.getWindowVisibleDisplayFrame(windowRect);
tabsContainer.getWindowVisibleDisplayFrame(windowRect);
int windowHeight = windowRect.bottom - windowRect.top;
// The web content area should have at least 1.5x the height of the action bar.
@ -338,9 +338,8 @@ public class TabsPanel extends LinearLayout
onLightweightThemeChanged();
}
// Panel View Container holds the ListView
static class PanelViewContainer extends FrameLayout {
public PanelViewContainer(Context context, AttributeSet attrs) {
static class TabsLayoutContainer extends FrameLayout {
public TabsLayoutContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@ -361,7 +360,7 @@ public class TabsPanel extends LinearLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!GeckoAppShell.getGeckoInterface().hasTabsSideBar()) {
int heightSpec = MeasureSpec.makeMeasureSpec(getPanelsContainerHeight(PanelViewContainer.this), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(getTabContainerHeight(TabsLayoutContainer.this), MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@ -483,7 +482,7 @@ public class TabsPanel extends LinearLayout
dispatchLayoutChange(getWidth(), getHeight());
} else {
int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
int height = actionBarHeight + getPanelsContainerHeight(mPanelsContainer);
int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
dispatchLayoutChange(getWidth(), height);
}
mHeaderVisible = true;
@ -502,7 +501,7 @@ public class TabsPanel extends LinearLayout
public void refresh() {
removeAllViews();
LayoutInflater.from(mContext).inflate(R.layout.tabs_panel, this);
inflateLayout(mContext);
initialize();
if (mVisible)
@ -541,13 +540,13 @@ public class TabsPanel extends LinearLayout
final int tabsPanelWidth = getWidth();
if (mVisible) {
ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
ViewHelper.setTranslationX(mPanelsContainer, -tabsPanelWidth);
ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
// The footer view is only present on the sidebar, v11+.
ViewHelper.setTranslationX(mFooter, -tabsPanelWidth);
}
final int translationX = (mVisible ? 0 : -tabsPanelWidth);
animator.attach(mPanelsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_X, translationX);
animator.attach(mFooter, PropertyAnimator.Property.TRANSLATION_X, translationX);
@ -557,16 +556,16 @@ public class TabsPanel extends LinearLayout
final int translationY = (mVisible ? 0 : -toolbarHeight);
if (mVisible) {
ViewHelper.setTranslationY(mHeader, -toolbarHeight);
ViewHelper.setTranslationY(mPanelsContainer, -toolbarHeight);
ViewHelper.setAlpha(mPanelsContainer, 0.0f);
ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight);
ViewHelper.setAlpha(mTabsContainer, 0.0f);
}
animator.attach(mPanelsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f);
animator.attach(mPanelsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY);
animator.attach(mTabsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f);
animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY);
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
}
mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mPanelsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
public void finishTabsAnimation() {
@ -575,10 +574,10 @@ public class TabsPanel extends LinearLayout
}
mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
mPanelsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
// If the tray is now hidden, call hide() on current panel and unset it as the current panel
// to avoid hide() being called again when the tray is opened next.
// If the tabs panel is now hidden, call hide() on current panel and unset it as the current panel
// to avoid hide() being called again when the layout is opened next.
if (!mVisible && mPanel != null) {
mPanel.hide();
mPanel = null;

Просмотреть файл

@ -25,6 +25,7 @@ import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.GeckoThread.LaunchState;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.RobocopUtils;
import org.mozilla.gecko.Tab;
@ -69,6 +70,8 @@ abstract class BaseTest extends BaseRobocopTest {
private static final int GECKO_READY_WAIT_MS = 180000;
public static final int MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS = 90000;
private static final String URL_HTTP_PREFIX = "http://";
private Activity mActivity;
private int mPreferenceRequestID = 0;
protected Solo mSolo;
@ -527,7 +530,25 @@ abstract class BaseTest extends BaseRobocopTest {
}
}
public final void verifyPageTitle(String title) {
public final void verifyPageTitle(final String title, String url) {
// We are asserting visible state - we shouldn't know if the title is null.
mAsserter.isnot(title, null, "The title argument is not null");
mAsserter.isnot(url, null, "The url argument is not null");
// TODO: We should also check the title bar preference.
final String expected;
if (!NewTabletUI.isEnabled(mActivity)) {
expected = title;
} else {
if (StringHelper.ABOUT_HOME_URL.equals(url)) {
expected = StringHelper.ABOUT_HOME_TITLE;
} else if (url.startsWith(URL_HTTP_PREFIX)) {
expected = url.substring(URL_HTTP_PREFIX.length());
} else {
expected = url;
}
}
final TextView urlBarTitle = (TextView) mSolo.getView(R.id.url_bar_title);
String pageTitle = null;
if (urlBarTitle != null) {
@ -536,7 +557,7 @@ abstract class BaseTest extends BaseRobocopTest {
waitForCondition(new VerifyTextViewText(urlBarTitle, title), MAX_WAIT_VERIFY_PAGE_TITLE_MS);
pageTitle = urlBarTitle.getText().toString();
}
mAsserter.is(pageTitle, title, "Page title is correct");
mAsserter.is(pageTitle, expected, "Page title is correct");
}
public final void verifyTabCount(int expectedTabCount) {
@ -619,23 +640,23 @@ abstract class BaseTest extends BaseRobocopTest {
/**
* Gets the AdapterView of the tabs list.
*
* @return List view in the tabs tray
* @return List view in the tabs panel
*/
private final AdapterView<ListAdapter> getTabsList() {
private final AdapterView<ListAdapter> getTabsLayout() {
Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
tabs.click();
return (AdapterView<ListAdapter>) getActivity().findViewById(R.id.normal_tabs);
}
/**
* Gets the view in the tabs tray at the specified index.
* Gets the view in the tabs panel at the specified index.
*
* @return View at index
*/
private View getTabViewAt(final int index) {
final View[] childView = { null };
final AdapterView<ListAdapter> view = getTabsList();
final AdapterView<ListAdapter> view = getTabsLayout();
runOnUiThreadSync(new Runnable() {
@Override

Просмотреть файл

@ -197,7 +197,7 @@ public abstract class SessionTest extends BaseTest {
verifyUrl(page.url);
} else {
waitForText(page.title);
verifyPageTitle(page.title);
verifyPageTitle(page.title, page.url);
}
}

Просмотреть файл

@ -9,9 +9,12 @@ import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertFalse;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.tests.StringHelper;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.tests.helpers.DeviceHelper;
import org.mozilla.gecko.tests.helpers.NavigationHelper;
import org.mozilla.gecko.tests.helpers.WaitHelper;
import android.view.View;
@ -26,6 +29,9 @@ import com.jayway.android.robotium.solo.Solo;
* A class representing any interactions that take place on the Toolbar.
*/
public class ToolbarComponent extends BaseComponent {
private static final String URL_HTTP_PREFIX = "http://";
public ToolbarComponent(final UITestContext testContext) {
super(testContext);
}
@ -40,7 +46,26 @@ public class ToolbarComponent extends BaseComponent {
return this;
}
public ToolbarComponent assertTitle(final String expected) {
public ToolbarComponent assertTitle(final String title, final String url) {
// We are asserting visible state - we shouldn't know if the title is null.
fAssertNotNull("The title argument is not null", title);
fAssertNotNull("The url argument is not null", url);
// TODO: We should also check the title bar preference.
final String expected;
if (!NewTabletUI.isEnabled(mActivity)) {
expected = title;
} else {
final String absoluteURL = NavigationHelper.adjustUrl(url);
if (StringHelper.ABOUT_HOME_URL.equals(absoluteURL)) {
expected = StringHelper.ABOUT_HOME_TITLE;
} else if (absoluteURL.startsWith(URL_HTTP_PREFIX)) {
expected = absoluteURL.substring(URL_HTTP_PREFIX.length());
} else {
expected = absoluteURL;
}
}
fAssertEquals("The Toolbar title is " + expected, expected, getTitle());
return this;
}
@ -91,7 +116,7 @@ public class ToolbarComponent extends BaseComponent {
return (ImageButton) getToolbarView().findViewById(R.id.edit_cancel);
}
private CharSequence getTitle() {
private String getTitle() {
return getTitleHelper(true);
}
@ -100,16 +125,16 @@ public class ToolbarComponent extends BaseComponent {
* may return a value that may never be visible to the user. Callers likely want to use
* {@link assertTitle} instead.
*/
public CharSequence getPotentiallyInconsistentTitle() {
public String getPotentiallyInconsistentTitle() {
return getTitleHelper(false);
}
private CharSequence getTitleHelper(final boolean shouldAssertNotEditing) {
private String getTitleHelper(final boolean shouldAssertNotEditing) {
if (shouldAssertNotEditing) {
assertIsNotEditing();
}
return getUrlTitleText().getText();
return getUrlTitleText().getText().toString();
}
private boolean isEditing() {

Просмотреть файл

@ -44,7 +44,7 @@ final public class NavigationHelper {
/**
* Returns a new URL with the docshell HTTP server host prefix.
*/
private static String adjustUrl(final String url) {
public static String adjustUrl(final String url) {
fAssertNotNull("url is not null", url);
if (url.startsWith("about:") || url.startsWith("chrome:")) {

Просмотреть файл

@ -66,7 +66,7 @@
#[testTabHistory]
# fails on gs2, nexus one, lg revolution, nexus s
#[testTabsTrayMenu]
#[testTabsLayoutMenu]
# fails on gs2, nexus one, lg revolution, nexus s
#[testThumbnails]

Просмотреть файл

@ -13,18 +13,20 @@ public class testAboutHomeVisibility extends UITest {
GeckoHelper.blockForReady();
// Check initial state on about:home.
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE, StringHelper.ABOUT_HOME_URL);
mAboutHome.assertVisible()
.assertCurrentPanel(PanelType.TOP_SITES);
// Go to blank 01.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mAboutHome.assertNotVisible();
// Go to blank 02.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
mAboutHome.assertNotVisible();
// Enter editing mode, where the about:home UI should be visible.
@ -38,7 +40,7 @@ public class testAboutHomeVisibility extends UITest {
// Loading about:home should show about:home again.
NavigationHelper.enterAndLoadUrl(StringHelper.ABOUT_HOME_URL);
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE, StringHelper.ABOUT_HOME_URL);
mAboutHome.assertVisible()
.assertCurrentPanel(PanelType.TOP_SITES);
@ -49,7 +51,5 @@ public class testAboutHomeVisibility extends UITest {
mAboutHome.navigateToBuiltinPanelType(PanelType.HISTORY)
.assertVisible()
.assertCurrentPanel(PanelType.HISTORY);
// TODO: Type in a url and assert the go button is visible.
}
}

Просмотреть файл

@ -2,34 +2,44 @@ package org.mozilla.gecko.tests;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.Element;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import android.app.Activity;
/* Tests related to the about: page:
* - check that about: loads from the URL bar
* - check that about: loads from Settings/About...
*/
public class testAboutPage extends PixelTest {
private void ensureTitleMatches(final String regex) {
Element urlBarTitle = mDriver.findElement(getActivity(), R.id.url_bar_title);
/**
* Ensures the page title matches the given regex (as opposed to String equality).
*/
private void ensureTitleMatches(final String titleRegex, final String urlRegex) {
final Activity activity = getActivity();
final Element urlBarTitle = mDriver.findElement(activity, R.id.url_bar_title);
// TODO: We should also be testing what the page title preference value is.
final String expectedTitle = NewTabletUI.isEnabled(activity) ? urlRegex : titleRegex;
mAsserter.isnot(urlBarTitle, null, "Got the URL bar title");
assertMatches(urlBarTitle.getText(), regex, "page title match");
assertMatches(urlBarTitle.getText(), expectedTitle, "page title match");
}
public void testAboutPage() {
blockForGeckoReady();
// Load the about: page and verify its title.
String url = "about:";
String url = StringHelper.ABOUT_SCHEME;
loadAndPaint(url);
ensureTitleMatches(StringHelper.ABOUT_LABEL);
ensureTitleMatches(StringHelper.ABOUT_LABEL, url);
// Open a new page to remove the about: page from the current tab.
url = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
inputAndLoadUrl(url);
// At this point the page title should have been set.
ensureTitleMatches(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, url);
// Set up listeners to catch the page load we're about to do.
Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
@ -45,6 +55,6 @@ public class testAboutPage extends PixelTest {
contentEventExpecter.unregisterListener();
// Grab the title to make sure the about: page was loaded.
ensureTitleMatches(StringHelper.ABOUT_LABEL);
ensureTitleMatches(StringHelper.ABOUT_LABEL, StringHelper.ABOUT_SCHEME);
}
}

Просмотреть файл

@ -61,7 +61,7 @@ public class testAddSearchEngine extends AboutHomeTest {
// Load the page for the search engine to add.
inputAndLoadUrl(searchEngineURL);
waitForText(StringHelper.ROBOCOP_SEARCH_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE, searchEngineURL);
// Used to long-tap on the search input box for the search engine to add.
getInstrumentation().waitForIdleSync();
@ -99,7 +99,7 @@ public class testAddSearchEngine extends AboutHomeTest {
mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
mAsserter.is(searchEngines.size(), initialNumSearchEngines + 1, "Checking the number of Search Engines has increased");
// Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event.
verifyDisplayedSearchEnginesCount(initialNumSearchEngines + 1);
searchEngineDataEventExpector.unregisterListener();
@ -151,7 +151,7 @@ public class testAddSearchEngine extends AboutHomeTest {
return (adapter.getCount() == expectedCount);
}
}, MAX_WAIT_TEST_MS);
// Exit about:home
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);

Просмотреть файл

@ -17,7 +17,7 @@ public class testAddonManager extends PixelTest {
public void testAddonManager() {
Actions.EventExpecter tabEventExpecter;
Actions.EventExpecter contentEventExpecter;
String url = StringHelper.ABOUT_ADDONS_URL;
final String aboutAddonsURL = StringHelper.ABOUT_ADDONS_URL;
blockForGeckoReady();
@ -36,21 +36,22 @@ public class testAddonManager extends PixelTest {
contentEventExpecter.unregisterListener();
// Verify the url
verifyPageTitle(StringHelper.ADDONS_LABEL);
verifyPageTitle(StringHelper.ADDONS_LABEL, aboutAddonsURL);
// Close the Add-on Manager
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
// Load the about:addons page and verify it was loaded
loadAndPaint(url);
verifyPageTitle(StringHelper.ADDONS_LABEL);
loadAndPaint(aboutAddonsURL);
verifyPageTitle(StringHelper.ADDONS_LABEL, aboutAddonsURL);
// Setup wait for tab to spawn and load
tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
// Open a new tab
addTab(getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL));
final String blankURL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
addTab(blankURL);
// Wait for the new tab and page to load
tabEventExpecter.blockForEvent();
@ -63,7 +64,7 @@ public class testAddonManager extends PixelTest {
verifyTabCount(2);
// Verify the page was opened
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, blankURL);
// Addons Manager is not opened 2 separate times when opened from the menu
selectMenuItem(StringHelper.ADDONS_LABEL);

Просмотреть файл

@ -48,7 +48,8 @@ public class testAppMenuPathways extends UITest {
// The above mock video playback test changes Java state, but not the associated JS state.
// Navigate to a new page so that the Java state is cleared.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// Test save as pdf functionality.
// The following call doesn't wait for the resulting pdf but checks that no exception are thrown.

Просмотреть файл

@ -32,7 +32,8 @@ public class testBookmark extends AboutHomeTest {
isBookmarkDisplayed(BOOKMARK_URL);
loadBookmark(BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mDatabaseHelper.deleteBookmark(BOOKMARK_URL);
waitForBookmarked(false);

Просмотреть файл

@ -52,7 +52,7 @@ public class testBookmarkFolders extends AboutHomeTest {
// Open the bookmark from a bookmark folder hierarchy
loadBookmark(DESKTOP_BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, DESKTOP_BOOKMARK_URL);
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
// Check that folders don't have a context menu

Просмотреть файл

@ -19,7 +19,8 @@ public class testBookmarkKeyword extends AboutHomeTest {
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
// Make sure the title of the page appeared.
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// Delete the bookmark to clean up.
mDatabaseHelper.deleteBookmark(url);

Просмотреть файл

@ -18,7 +18,9 @@ public class testBookmarklets extends AboutHomeTest {
// load a standard page so bookmarklets work
inputAndLoadUrl(url);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); // Waiting for page title to ensure the page is loaded
// Waiting for page title to ensure the page is loaded
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// verify that user-entered bookmarklets do *not* work
enterUrl(js);

Просмотреть файл

@ -30,14 +30,14 @@ public class testClearPrivateData extends PixelTest {
String blank2 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
inputAndLoadUrl(blank1);
verifyPageTitle(title);
verifyPageTitle(title, blank1);
mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, blank2);
// Checking that the history list is not empty
verifyHistoryCount(1);
//clear and check for device
checkDevice(title);
checkDevice(title, blank1);
// Checking that history list is empty
verifyHistoryCount(0);
@ -65,7 +65,7 @@ public class testClearPrivateData extends PixelTest {
checkOption(shareStrings[3], "Cancel");
loadCheckDismiss(shareStrings[2], url, shareStrings[0]);
checkOption(shareStrings[2], "Cancel");
checkDevice(titleGeolocation);
checkDevice(titleGeolocation, url);
}
public void clearPassword(){
@ -75,24 +75,20 @@ public class testClearPrivateData extends PixelTest {
loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]);
checkOption(passwordStrings[1], "Clear");
loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]);
checkDevice(title);
checkDevice(title, getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL));
}
// clear private data and verify the device type because for phone there is an extra back action to exit the settings menu
public void checkDevice(String title) {
public void checkDevice(final String title, final String url) {
clearPrivateData();
if (mDevice.type.equals("phone")) {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
mAsserter.ok(waitForText(StringHelper.PRIVACY_SECTION_LABEL), "waiting to perform one back", "one back");
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
verifyPageTitle(title);
}
else {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
verifyPageTitle(title);
}
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
verifyPageTitle(title, url);
}
// Load a URL, verify that the doorhanger appears and dismiss it
public void loadCheckDismiss(String option, String url, String message) {
inputAndLoadUrl(url);

Просмотреть файл

@ -17,11 +17,11 @@ public class testHistory extends AboutHomeTest {
String url3 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
inputAndLoadUrl(url);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_URL, url);
inputAndLoadUrl(url2);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_URL, url2);
inputAndLoadUrl(url3);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL, url3);
openAboutHomeTab(AboutHomeTabs.HISTORY);
@ -62,7 +62,7 @@ public class testHistory extends AboutHomeTest {
// The first item here (since it was just visited) should be a "Switch to tab" item
// i.e. don't expect a DOMContentLoaded event
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL, StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
verifyUrl(url3);
}
}

Просмотреть файл

@ -24,8 +24,9 @@ public class testInputConnection extends UITest {
public void testInputConnection() throws InterruptedException {
GeckoHelper.blockForReady();
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_INPUT_URL + "#" + INITIAL_TEXT);
mToolbar.assertTitle(StringHelper.ROBOCOP_INPUT_TITLE);
final String url = StringHelper.ROBOCOP_INPUT_URL + "#" + INITIAL_TEXT;
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(StringHelper.ROBOCOP_INPUT_TITLE, url);
mGeckoView.mTextInput
.waitForInputConnection()

Просмотреть файл

@ -18,7 +18,7 @@ public class testPictureLinkContextMenu extends ContentContextMenuTest {
PICTURE_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_PICTURE_LINK_URL);
BLANK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
loadAndPaint(PICTURE_PAGE_URL);
verifyPageTitle(PICTURE_PAGE_TITLE);
verifyPageTitle(PICTURE_PAGE_TITLE, PICTURE_PAGE_URL);
switchTabs(imageTitle);
verifyContextMenuItems(photoMenuItems);

Просмотреть файл

@ -73,7 +73,7 @@ public class testReaderMode extends AboutHomeTest {
contentPageShowExpecter.unregisterListener();
paintExpecter.blockUntilClear(EVENT_CLEAR_DELAY_MS);
paintExpecter.unregisterListener();
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
// Open the share menu for the reader toolbar
height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
@ -134,7 +134,7 @@ public class testReaderMode extends AboutHomeTest {
mSolo.clickOnView(child);
contentEventExpecter.blockForEvent();
contentEventExpecter.unregisterListener();
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
// Verify that we are in reader mode and remove the page from Reading List
height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
@ -142,7 +142,7 @@ public class testReaderMode extends AboutHomeTest {
mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
mSolo.clickOnScreen(width,height);
mAsserter.ok(mSolo.waitForText("Page removed from your Reading List"), "Waiting for the page to removed from your Reading List", "The page is removed from your Reading List");
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
//Check if the Reading List is empty
openAboutHomeTab(AboutHomeTabs.READING_LIST);

Просмотреть файл

@ -16,8 +16,9 @@ public class testSelectionHandler extends UITest {
GeckoHelper.blockForReady();
Actions.EventExpecter robocopTestExpecter = getActions().expectGeckoEvent("Robocop:testSelectionHandler");
NavigationHelper.enterAndLoadUrl("chrome://roboextender/content/testSelectionHandler.html");
mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE);
final String url = "chrome://roboextender/content/testSelectionHandler.html";
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE, url);
while (!test(robocopTestExpecter)) {
// do nothing

Просмотреть файл

@ -10,25 +10,32 @@ public class testSessionHistory extends UITest {
public void testSessionHistory() {
GeckoHelper.blockForReady();
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
String url = StringHelper.ROBOCOP_BLANK_PAGE_01_URL;
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, url);
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
url = StringHelper.ROBOCOP_BLANK_PAGE_02_URL;
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, url);
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_TITLE);
url = StringHelper.ROBOCOP_BLANK_PAGE_03_URL;
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_TITLE, url);
NavigationHelper.goBack();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
NavigationHelper.goBack();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
NavigationHelper.goForward();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
NavigationHelper.reload();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
}
}

Просмотреть файл

@ -37,7 +37,7 @@ public class testShareLink extends AboutHomeTest {
openAboutHomeTab(AboutHomeTabs.READING_LIST);
inputAndLoadUrl(url);
verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
verifyPageTitle(urlTitle, url); // Waiting for page title to ensure the page is loaded
selectMenuItem(StringHelper.SHARE_LABEL);
if (Build.VERSION.SDK_INT >= 14) {
@ -248,7 +248,7 @@ public class testShareLink extends AboutHomeTest {
public boolean test() {
ArrayList<View> views = mSolo.getCurrentViews();
for (View view : views) {
// List may be displayed in different view formats.
// List may be displayed in different view formats.
// On JB, GridView is common; on ICS-, ListView is common.
if (view instanceof ListView ||
view instanceof GridView) {

Просмотреть файл

@ -1,5 +1,7 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.NewTabletUI;
/**
* This patch tests the option that shows the full URL and title in the URL Bar
@ -7,6 +9,11 @@ import org.mozilla.gecko.Actions;
public class testTitleBar extends PixelTest {
public void testTitleBar() {
// Because there is no title bar option on new tablet, we don't need to run this test.
if (NewTabletUI.isEnabled(getActivity())) {
return;
}
blockForGeckoReady();
checkOption();
}
@ -18,7 +25,7 @@ public class testTitleBar extends PixelTest {
// Loading a page
inputAndLoadUrl(blank1);
verifyPageTitle(title);
verifyPageTitle(title, blank1);
// Ensure the full URL is displayed in the URL Bar
selectOption(StringHelper.SHOW_PAGE_ADDRESS_LABEL);
@ -28,7 +35,7 @@ public class testTitleBar extends PixelTest {
// Ensure the title is displayed in the URL Bar
selectOption(StringHelper.SHOW_PAGE_TITLE_LABEL);
inputAndLoadUrl(blank1);
verifyPageTitle(title);
verifyPageTitle(title, blank1);
}
// Entering settings, changing the options: show title/page address option and verifing the device type because for phone there is an extra back action to exit the settings menu

Просмотреть файл

@ -36,6 +36,8 @@ public class TabCounter extends ThemedTextSwitcher
private final int mLayoutId;
private int mCount;
public static final int MAX_VISIBLE_TABS = 99;
public static final String SO_MANY_TABS_OPEN = "";
private enum FadeMode {
FADE_IN,
@ -84,6 +86,12 @@ public class TabCounter extends ThemedTextSwitcher
return;
}
// don't animate if there are still over MAX_VISIBLE_TABS tabs open
if (mCount > MAX_VISIBLE_TABS && count > MAX_VISIBLE_TABS) {
mCount = count;
return;
}
if (count < mCount) {
setInAnimation(mFlipInBackward);
setOutAnimation(mFlipOutForward);
@ -97,14 +105,21 @@ public class TabCounter extends ThemedTextSwitcher
setDisplayedChild(0);
// Set In value, trigger animation to Out value
setCurrentText(String.valueOf(mCount));
setText(String.valueOf(count));
setCurrentText(formatForDisplay(mCount));
setText(formatForDisplay(count));
mCount = count;
}
private String formatForDisplay(int count) {
if (count > MAX_VISIBLE_TABS) {
return SO_MANY_TABS_OPEN;
}
return String.valueOf(count);
}
void setCount(int count) {
setCurrentText(String.valueOf(count));
setCurrentText(formatForDisplay(count));
mCount = count;
}

Просмотреть файл

@ -83,7 +83,7 @@ public class IconTabWidget extends TabWidget {
if (!mIsIcon) {
return null;
}
// We can have multiple views in the tray for each child. This finds the
// We can have multiple views in the tabs panel for each child. This finds the
// first view corresponding to the given tab. This varies by Android
// version. The first view should always be our ImageButton, but let's
// be safe.
@ -98,7 +98,7 @@ public class IconTabWidget extends TabWidget {
if (!mIsIcon) {
return;
}
// We can have multiple views in the tray for each child. This finds the
// We can have multiple views in the tabs panel for each child. This finds the
// first view corresponding to the given tab. This varies by Android
// version. The first view should always be our ImageButton, but let's
// be safe.

Просмотреть файл

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var WebcompatReporter = {
menuItem: null,
menuItemEnabled: null,
init: function() {
Services.obs.addObserver(this, "DesktopMode:Change", false);
Services.obs.addObserver(this, "content-page-shown", false);
this.addMenuItem();
},
uninit: function() {
Services.obs.removeObserver(this, "DesktopMode:Change");
if (this.menuItem) {
NativeWindow.menu.remove(this.menuItem);
this.menuItem = null;
}
},
observe: function(subject, topic, data) {
if (topic === "content-page-shown") {
let currentURI = subject.documentURI;
if (!this.menuItemEnabled && this.isReportableUrl(currentURI)) {
NativeWindow.menu.update(this.menuItem, {enabled: true});
this.menuItemEnabled = true;
} else if (this.menuItemEnabled && !this.isReportableUrl(currentURI)) {
NativeWindow.menu.update(this.menuItem, {enabled: false});
this.menuItemEnabled = false;
}
} else if (topic === "DesktopMode:Change") {
let args = JSON.parse(data);
let tab = BrowserApp.getTabForId(args.tabId);
if (args.desktopMode && tab !== null) {
this.reportDesktopModePrompt();
}
}
},
addMenuItem: function() {
this.menuItem = NativeWindow.menu.add({
name: this.strings.GetStringFromName("webcompat.menu.name"),
callback: () => {
let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
this.reportIssue(currentURI);
},
enabled: false,
});
},
isReportableUrl: function(url) {
return url !== null && !(url.startsWith("about") ||
url.startsWith("chrome") ||
url.startsWith("file") ||
url.startsWith("resource"));
},
reportDesktopModePrompt: function() {
let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
let message = this.strings.GetStringFromName("webcompat.reportDesktopMode.message");
let options = {
button: {
label: this.strings.GetStringFromName("webcompat.reportDesktopModeYes.label"),
callback: () => this.reportIssue(currentURI)
}
};
NativeWindow.toast.show(message, "long", options);
},
reportIssue: function(url) {
let webcompatURL = new URL("http://webcompat.com/");
webcompatURL.searchParams.append("open", "1");
webcompatURL.searchParams.append("url", url);
BrowserApp.addTab(webcompatURL.href);
}
};
XPCOMUtils.defineLazyGetter(WebcompatReporter, "strings", function() {
return Services.strings.createBundle("chrome://browser/locale/webcompatReporter.properties");
});

Просмотреть файл

@ -112,6 +112,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
["Linkifier", "chrome://browser/content/Linkify.js"],
["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
["CastingApps", "chrome://browser/content/CastingApps.js"],
#ifdef NIGHTLY_BUILD
["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"],
#endif
].forEach(function (aScript) {
let [name, script] = aScript;
XPCOMUtils.defineLazyGetter(window, name, function() {
@ -334,6 +337,9 @@ var BrowserApp = {
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
SafeBrowsing.init();
}, Ci.nsIThread.DISPATCH_NORMAL);
#endif
#ifdef NIGHTLY_BUILD
WebcompatReporter.init();
#endif
} catch(ex) { console.log(ex); }
}, false);
@ -858,6 +864,9 @@ var BrowserApp = {
CastingApps.uninit();
Distribution.uninit();
Tabs.uninit();
#ifdef NIGHTLY_BUILD
WebcompatReporter.uninit();
#endif
},
// This function returns false during periods where the browser displayed document is

Просмотреть файл

@ -62,6 +62,9 @@ chrome.jar:
content/aboutDevices.xhtml (content/aboutDevices.xhtml)
content/aboutDevices.js (content/aboutDevices.js)
#endif
#ifdef NIGHTLY_BUILD
content/WebcompatReporter.js (content/WebcompatReporter.js)
#endif
% content branding %content/branding/

Просмотреть файл

@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# LOCALIZATION NOTE (webcompat.menu.name): A "site issue" is a bug, display,
# or functionality problem with a webpage in the browser.
webcompat.menu.name=Report Site Issue
# LOCALIZATION NOTE (webcompat.reportDesktopMode.message): A " site issue" is a
# bug, display, or functionality problem with a webpage in the browser.
webcompat.reportDesktopMode.message=Report site issue?
webcompat.reportDesktopModeYes.label=Report

Просмотреть файл

@ -39,6 +39,9 @@
locale/@AB_CD@/browser/payments.properties (%chrome/payments.properties)
locale/@AB_CD@/browser/handling.properties (%chrome/handling.properties)
locale/@AB_CD@/browser/webapp.properties (%chrome/webapp.properties)
#ifdef NIGHTLY_BUILD
locale/@AB_CD@/browser/webcompatReporter.properties (%chrome/webcompatReporter.properties)
#endif
# overrides for toolkit l10n, also for en-US
relativesrcdir toolkit/locales:

Просмотреть файл

@ -5,171 +5,33 @@
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
const CONFIG = { iceServers: [{ "url": "stun:stun.services.mozilla.com" }] };
let log = Cu.import("resource://gre/modules/AndroidLog.jsm",
{}).AndroidLog.d.bind(null, "TabMirror");
let failure = function(x) {
log("ERROR: " + JSON.stringify(x));
};
let TabMirror = function(deviceId, window) {
let out_queue = [];
let in_queue = [];
let DEBUG = false;
let log = Cu.import("resource://gre/modules/AndroidLog.jsm",
{}).AndroidLog.d.bind(null, "TabMirror");
this.deviceId = deviceId;
// Save mozRTCSessionDescription and mozRTCIceCandidate for later when the window object is not available.
this.RTCSessionDescription = window.mozRTCSessionDescription;
this.RTCIceCandidate = window.mozRTCIceCandidate;
let RTCPeerConnection = window.mozRTCPeerConnection;
let RTCSessionDescription = window.mozRTCSessionDescription;
let RTCIceCandidate = window.mozRTCIceCandidate;
let getUserMedia = window.navigator.mozGetUserMedia.bind(window.navigator);
Services.obs.addObserver((aSubject, aTopic, aData) => this._processMessage(aData), "MediaPlayer:Response", false);
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
in_queue.push(aData);
}, "MediaPlayer:Response", false);
this._pc = new window.mozRTCPeerConnection(CONFIG, {});
let poll_timeout = 1000; // ms
let audio_stream = undefined;
let video_stream = undefined;
let pc = undefined;
let poll_success = function(msg) {
if (!msg) {
poll_error();
return;
}
let js;
try {
js = JSON.parse(msg);
} catch(ex) {
log("ex: " + ex);
}
let sdp = js.body;
if (sdp) {
if (sdp.sdp) {
if (sdp.type === "offer") {
process_offer(sdp);
} else if (sdp.type === "answer") {
process_answer(sdp);
}
} else {
process_ice_candidate(sdp);
}
}
window.setTimeout(poll, poll_timeout);
};
let poll_error = function (msg) {
window.setTimeout(poll, poll_timeout);
};
let poll = function () {
if (in_queue) {
poll_success(in_queue.pop());
}
};
let failure = function(x) {
log("ERROR: " + JSON.stringify(x));
};
// Signaling methods
let send_sdpdescription= function(sdp) {
let msg = {
body: sdp
};
sendMessage(JSON.stringify(msg));
};
let deobjify = function(x) {
return JSON.parse(JSON.stringify(x));
};
let process_offer = function(sdp) {
pc.setRemoteDescription(new RTCSessionDescription(sdp),
set_remote_offer_success, failure);
};
let process_answer = function(sdp) {
pc.setRemoteDescription(new RTCSessionDescription(sdp),
set_remote_answer_success, failure);
};
let process_ice_candidate = function(msg) {
pc.addIceCandidate(new RTCIceCandidate(msg));
};
let set_remote_offer_success = function() {
pc.createAnswer(create_answer_success, failure);
};
let set_remote_answer_success= function() {
};
let set_local_success_offer = function(sdp) {
send_sdpdescription(sdp);
};
let set_local_success_answer = function(sdp) {
send_sdpdescription(sdp);
};
let filter_nonrelay_candidates = function(sdp) {
let lines = sdp.sdp.split("\r\n");
let lines2 = lines.filter(function(x) {
if (!/candidate/.exec(x))
return true;
if (/relay/.exec(x))
return true;
return false;
});
sdp.sdp = lines2.join("\r\n");
};
let create_offer_success = function(sdp) {
pc.setLocalDescription(sdp,
function() {
set_local_success_offer(sdp);
},
failure);
};
let create_answer_success = function(sdp) {
pc.setLocalDescription(sdp,
function() {
set_local_success_answer(sdp);
},
failure);
};
let on_ice_candidate = function (candidate) {
send_sdpdescription(candidate.candidate);
};
let ready = function() {
pc.createOffer(create_offer_success, failure);
poll();
};
let config = {
iceServers: [{ "url": "stun:stun.services.mozilla.com" }]
};
pc = new RTCPeerConnection(config, {});
if (!pc) {
log("Failure creating Webrtc object");
return;
if (!this._pc) {
throw "Failure creating Webrtc object";
}
pc.onicecandidate = on_ice_candidate;
this._pc.onicecandidate = this._onIceCandidate.bind(this);
let windowId = window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
let viewport = window.BrowserApp.selectedTab.getViewport();
@ -203,41 +65,95 @@ let TabMirror = function(deviceId, window) {
}
};
let gUM_success = function(stream){
pc.addStream(stream);
ready();
};
window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this));
};
let gUM_failure = function() {
log("Could not get video stream");
};
TabMirror.prototype = {
getUserMedia( constraints, gUM_success, gUM_failure);
_processMessage: function(data) {
function sendMessage(msg) {
let obj = {
type: "MediaPlayer:Message",
id: deviceId,
data: msg
};
if (deviceId) {
Services.androidBridge.handleGeckoMessage(obj);
if (!data) {
return;
}
}
return {
stop: function() {
let msg = JSON.parse(data);
if (!msg) {
return;
}
if (msg.sdp) {
if (msg.type === "answer") {
this._processAnswer(msg);
} else {
log("Unandled sdp message type: " + msg.type);
}
} else {
this._processIceCandidate(msg);
}
},
// Signaling methods
_processAnswer: function(msg) {
this._pc.setRemoteDescription(new this.RTCSessionDescription(msg),
this._setRemoteAnswerSuccess.bind(this), failure);
},
_processIceCandidate: function(msg) {
// WebRTC generates a warning if the success and fail callbacks are not passed in.
this._pc.addIceCandidate(new this.RTCIceCandidate(msg), () => log("Ice Candiated added successfuly"), () => log("Failed to add Ice Candidate"));
},
_setRemoteAnswerSuccess: function() {
},
_setLocalSuccessOffer: function(sdp) {
this._sendMessage(sdp);
},
_createOfferSuccess: function(sdp) {
this._pc.setLocalDescription(sdp, () => this._setLocalSuccessOffer(sdp), failure);
},
_onIceCandidate: function (msg) {
log("NEW Ice Candidate: " + JSON.stringify(msg.candidate));
this._sendMessage(msg.candidate);
},
_ready: function() {
this._pc.createOffer(this._createOfferSuccess.bind(this), failure);
},
_onGumSuccess: function(stream){
this._pc.addStream(stream);
this._ready();
},
_onGumFailure: function() {
log("Could not get video stream");
this._pc.close();
},
_sendMessage: function(msg) {
if (this.deviceId) {
let obj = {
type: "MediaPlayer:Message",
id: this.deviceId,
data: JSON.stringify(msg)
};
Messaging.sendRequest(obj);
}
},
stop: function() {
if (this.deviceId) {
let obj = {
type: "MediaPlayer:End",
id: deviceId
id: this.deviceId
};
if (deviceId) {
Services.androidBridge.handleGeckoMessage(obj);
}
Services.androidBridge.handleGeckoMessage(obj);
}
}
},
};