This commit is contained in:
Phil Ringnalda 2013-12-29 17:34:00 -08:00
Родитель 54a2b48649 b97ade4cd5
Коммит 72e4c181e6
12 изменённых файлов: 280 добавлений и 488 удалений

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

@ -19,288 +19,28 @@ XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest",
"@mozilla.org/dom/dom-request-service;1",
"nsIDOMRequestService");
XPCOMUtils.defineLazyServiceGetter(this, "pm",
"@mozilla.org/permissionmanager;1",
"nsIPermissionManager");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
const CONTACTS_SENDMORE_MINIMUM = 5;
function ContactAddressImpl() { }
ContactAddressImpl.prototype = {
// This function is meant to be called via bindings code for type checking,
// don't call it directly. Instead, create a content object and call initialize
// on that.
initialize: function(aType, aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName, aPref) {
this.type = aType;
this.streetAddress = aStreetAddress;
this.locality = aLocality;
this.region = aRegion;
this.postalCode = aPostalCode;
this.countryName = aCountryName;
this.pref = aPref;
},
toJSON: function(excludeExposedProps) {
let json = {
type: this.type,
streetAddress: this.streetAddress,
locality: this.locality,
region: this.region,
postalCode: this.postalCode,
countryName: this.countryName,
pref: this.pref,
};
if (!excludeExposedProps) {
json.__exposedProps__ = {
type: "rw",
streetAddress: "rw",
locality: "rw",
region: "rw",
postalCode: "rw",
countryName: "rw",
pref: "rw",
};
}
return json;
},
classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"),
contractID: "@mozilla.org/contactAddress;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
};
function ContactFieldImpl() { }
ContactFieldImpl.prototype = {
// This function is meant to be called via bindings code for type checking,
// don't call it directly. Instead, create a content object and call initialize
// on that.
initialize: function(aType, aValue, aPref) {
this.type = aType;
this.value = aValue;
this.pref = aPref;
},
toJSON: function(excludeExposedProps) {
let json = {
type: this.type,
value: this.value,
pref: this.pref,
};
if (!excludeExposedProps) {
json.__exposedProps__ = {
type: "rw",
value: "rw",
pref: "rw",
};
}
return json;
},
classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"),
contractID: "@mozilla.org/contactField;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
};
function ContactTelFieldImpl() { }
ContactTelFieldImpl.prototype = {
// This function is meant to be called via bindings code for type checking,
// don't call it directly. Instead, create a content object and call initialize
// on that.
initialize: function(aType, aValue, aCarrier, aPref) {
this.type = aType;
this.value = aValue;
this.carrier = aCarrier;
this.pref = aPref;
},
toJSON: function(excludeExposedProps) {
let json = {
type: this.type,
value: this.value,
carrier: this.carrier,
pref: this.pref,
};
if (!excludeExposedProps) {
json.__exposedProps__ = {
type: "rw",
value: "rw",
carrier: "rw",
pref: "rw",
};
}
return json;
},
classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"),
contractID: "@mozilla.org/contactTelField;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
};
function validateArrayField(data, createCb) {
// We use an array-like Proxy to validate data set by content, since we don't
// have WebIDL arrays yet. See bug 851726.
// ArrayPropertyExposedPropsProxy is used to return "rw" for any valid index
// and "length" in __exposedProps__.
const ArrayPropertyExposedPropsProxy = new Proxy({}, {
get: function(target, name) {
// Test for index access
if (String(name >>> 0) === name) {
return "rw";
}
if (name === "length") {
return "r";
}
}
});
const ArrayPropertyHandler = {
set: function(target, name, val, receiver) {
// Test for index access
if (String(name >>> 0) === name) {
target[name] = createCb(val);
}
},
get: function(target, name) {
if (name === "__exposedProps__") {
return ArrayPropertyExposedPropsProxy;
}
return target[name];
}
};
if (data === null || data === undefined) {
return undefined;
}
data = Array.isArray(data) ? data : [data];
let filtered = [];
for (let i = 0, n = data.length; i < n; ++i) {
if (data[i]) {
filtered.push(createCb(data[i]));
}
}
return new Proxy(filtered, ArrayPropertyHandler);
}
// We need this to create a copy of the mozContact object in ContactManager.save
// Keep in sync with the interfaces.
const PROPERTIES = [
"name", "honorificPrefix", "givenName", "additionalName", "familyName",
"honorificSuffix", "nickname", "photo", "category", "org", "jobTitle",
"bday", "note", "anniversary", "sex", "genderIdentity", "key"
"bday", "note", "anniversary", "sex", "genderIdentity", "key", "adr", "email",
"url", "impp", "tel"
];
const ADDRESS_PROPERTIES = ["adr"];
const FIELD_PROPERTIES = ["email", "url", "impp"];
const TELFIELD_PROPERTIES = ["tel"];
function Contact() { }
Contact.prototype = {
// We need to create the content interfaces in these setters, otherwise when
// we return these objects (e.g. from a find call), the values in the array
// will be COW's, and content cannot see the properties.
set email(aEmail) {
this._email = aEmail;
},
get email() {
this._email = validateArrayField(this._email, function(email) {
let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
obj.initialize(email.type, email.value, email.pref);
return obj;
}.bind(this));
return this._email;
},
set adr(aAdr) {
this._adr = aAdr;
},
get adr() {
this._adr = validateArrayField(this._adr, function(adr) {
let obj = this._window.ContactAddress._create(this._window, new ContactAddressImpl());
obj.initialize(adr.type, adr.streetAddress, adr.locality,
adr.region, adr.postalCode, adr.countryName,
adr.pref);
return obj;
}.bind(this));
return this._adr;
},
set tel(aTel) {
this._tel = aTel;
},
get tel() {
this._tel = validateArrayField(this._tel, function(tel) {
let obj = this._window.ContactTelField._create(this._window, new ContactTelFieldImpl());
obj.initialize(tel.type, tel.value, tel.carrier, tel.pref);
return obj;
}.bind(this));
return this._tel;
},
set impp(aImpp) {
this._impp = aImpp;
},
get impp() {
this._impp = validateArrayField(this._impp, function(impp) {
let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
obj.initialize(impp.type, impp.value, impp.pref);
return obj;
}.bind(this));
return this._impp;
},
set url(aUrl) {
this._url = aUrl;
},
get url() {
this._url = validateArrayField(this._url, function(url) {
let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
obj.initialize(url.type, url.value, url.pref);
return obj;
}.bind(this));
return this._url;
},
init: function(aWindow) {
this._window = aWindow;
},
__init: function(aProp) {
this.name = aProp.name;
this.honorificPrefix = aProp.honorificPrefix;
this.givenName = aProp.givenName;
this.additionalName = aProp.additionalName;
this.familyName = aProp.familyName;
this.honorificSuffix = aProp.honorificSuffix;
this.nickname = aProp.nickname;
this.email = aProp.email;
this.photo = aProp.photo;
this.url = aProp.url;
this.category = aProp.category;
this.adr = aProp.adr;
this.tel = aProp.tel;
this.org = aProp.org;
this.jobTitle = aProp.jobTitle;
this.bday = aProp.bday;
this.note = aProp.note;
this.impp = aProp.impp;
this.anniversary = aProp.anniversary;
this.sex = aProp.sex;
this.genderIdentity = aProp.genderIdentity;
this.key = aProp.key;
for (let prop in aProp) {
this[prop] = aProp[prop];
}
},
setMetadata: function(aId, aPublished, aUpdated) {
@ -313,69 +53,9 @@ Contact.prototype = {
}
},
toJSON: function() {
return {
id: this.id,
published: this.published,
updated: this.updated,
name: this.name,
honorificPrefix: this.honorificPrefix,
givenName: this.givenName,
additionalName: this.additionalName,
familyName: this.familyName,
honorificSuffix: this.honorificSuffix,
nickname: this.nickname,
category: this.category,
org: this.org,
jobTitle: this.jobTitle,
note: this.note,
sex: this.sex,
genderIdentity: this.genderIdentity,
email: this.email,
photo: this.photo,
adr: this.adr,
url: this.url,
tel: this.tel,
bday: this.bday,
impp: this.impp,
anniversary: this.anniversary,
key: this.key,
__exposedProps__: {
id: "rw",
published: "rw",
updated: "rw",
name: "rw",
honorificPrefix: "rw",
givenName: "rw",
additionalName: "rw",
familyName: "rw",
honorificSuffix: "rw",
nickname: "rw",
category: "rw",
org: "rw",
jobTitle: "rw",
note: "rw",
sex: "rw",
genderIdentity: "rw",
email: "rw",
photo: "rw",
adr: "rw",
url: "rw",
tel: "rw",
bday: "rw",
impp: "rw",
anniversary: "rw",
key: "rw",
}
};
},
classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"),
contractID: "@mozilla.org/contact;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIDOMGlobalPropertyInitializer]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
};
function ContactManager() { }
@ -599,50 +279,19 @@ ContactManager.prototype = {
// mozContact object.
let newContact = {properties: {}};
for (let field of PROPERTIES) {
if (aContact[field]) {
try {
for (let field of PROPERTIES) {
// This hack makes sure modifications to the sequence attributes get validated.
aContact[field] = aContact[field];
newContact.properties[field] = aContact[field];
}
}
for (let prop of ADDRESS_PROPERTIES) {
if (aContact[prop]) {
newContact.properties[prop] = [];
for (let i of aContact[prop]) {
if (i) {
let json = ContactAddressImpl.prototype.toJSON.apply(i, [true]);
newContact.properties[prop].push(json);
}
}
}
}
for (let prop of FIELD_PROPERTIES) {
if (aContact[prop]) {
newContact.properties[prop] = [];
for (let i of aContact[prop]) {
if (i) {
let json = ContactFieldImpl.prototype.toJSON.apply(i, [true]);
newContact.properties[prop].push(json);
}
}
}
}
for (let prop of TELFIELD_PROPERTIES) {
if (aContact[prop]) {
newContact.properties[prop] = [];
for (let i of aContact[prop]) {
if (i) {
let json = ContactTelFieldImpl.prototype.toJSON.apply(i, [true]);
newContact.properties[prop].push(json);
}
}
}
} catch (e) {
// And then make sure we throw a proper error message (no internal file and line #)
throw new this._window.DOMError(e.name, e.message);
}
let request = this.createRequest();
let requestID = this.getRequestId({request: request, reason: reason});
let requestID = this.getRequestId({request: request});
let reason;
if (aContact.id == "undefined") {
@ -816,5 +465,5 @@ ContactManager.prototype = {
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl
Contact, ContactManager
]);

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

@ -1,12 +1,3 @@
component {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33} ContactManager.js
contract @mozilla.org/contactAddress;1 {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}
component {ad19a543-69e4-44f0-adfa-37c011556bc1} ContactManager.js
contract @mozilla.org/contactField;1 {ad19a543-69e4-44f0-adfa-37c011556bc1}
component {4d42c5a9-ea5d-4102-80c3-40cc986367ca} ContactManager.js
contract @mozilla.org/contactTelField;1 {4d42c5a9-ea5d-4102-80c3-40cc986367ca}
component {72a5ee28-81d8-4af8-90b3-ae935396cc66} ContactManager.js
contract @mozilla.org/contact;1 {72a5ee28-81d8-4af8-90b3-ae935396cc66}

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

@ -552,7 +552,8 @@ var steps = [
honorificPrefix: [],
honorificSuffix: [{foo: "bar"}],
sex: 17,
genderIdentity: 18
genderIdentity: 18,
email: [{type: ["foo"], value: "bar"}]
};
obj.honorificPrefix.__defineGetter__('0',(function() {
var c = 0;
@ -566,13 +567,15 @@ var steps = [
}
})());
createResult1 = new mozContact(obj);
createResult1.email.push({aeiou: "abcde"});
req = mozContacts.save(createResult1);
req.onsuccess = function () {
checkContacts(createResult1, {
honorificPrefix: ["string"],
honorificSuffix: ["[object Object]"],
sex: "17",
genderIdentity: "18"
genderIdentity: "18",
email: [{type: ["foo"], value: "bar"}, {}]
});
next();
};
@ -696,13 +699,13 @@ var steps = [
impp: [{value: undefined}],
tel: [{value: undefined}],
});
ise(c.adr[0].streetAddress, null, "adr.streetAddress is null");
ise(c.adr[0].locality, null, "adr.locality is null");
ise(c.adr[0].pref, null, "adr.pref is null");
ise(c.email[0].value, null, "email.value is null");
ise(c.url[0].value, null, "url.value is null");
ise(c.impp[0].value, null, "impp.value is null");
ise(c.tel[0].value, null, "tel.value is null");
ise(c.adr[0].streetAddress, undefined, "adr.streetAddress is undefined");
ise(c.adr[0].locality, undefined, "adr.locality is undefined");
ise(c.adr[0].pref, undefined, "adr.pref is undefined");
ise(c.email[0].value, undefined, "email.value is undefined");
ise(c.url[0].value, undefined, "url.value is undefined");
ise(c.impp[0].value, undefined, "impp.value is undefined");
ise(c.tel[0].value, undefined, "tel.value is undefined");
next();
},
function() {
@ -721,6 +724,50 @@ var steps = [
testArrayProp("url");
next();
},
function() {
ok(true, "Passing a mozContact with invalid data to save() should throw");
var c = new mozContact({
photo: [],
tel: []
});
c.photo.push({});
SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in Blob array");
c.tel.push(123);
SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in dictionary array");
next();
},
function() {
ok(true, "Inline changes to array properties should be seen by save");
var c = new mozContact({
name: [],
familyName: [],
givenName: [],
nickname: [],
tel: [],
adr: [],
email: []
});
for (var prop of Object.getOwnPropertyNames(properties1)) {
if (!Array.isArray(properties1[prop])) {
continue;
}
for (var i = 0; i < properties1[prop].length; ++i) {
c[prop].push(properties1[prop][i]);
}
}
req = navigator.mozContacts.save(c);
req.onsuccess = function() {
req = navigator.mozContacts.find(defaultOptions);
req.onsuccess = function() {
ise(req.result.length, 1, "Got 1 contact");
checkContacts(req.result[0], properties1);
next();
};
req.onerror = onFailure;
};
req.onerror = onFailure;
},
clearDatabase,
function () {
ok(true, "all done!\n");
SimpleTest.finish();

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

@ -4,29 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactAddress;1"]
interface ContactAddress {
attribute object? type; // DOMString[]
attribute DOMString? streetAddress;
attribute DOMString? locality;
attribute DOMString? region;
attribute DOMString? postalCode;
attribute DOMString? countryName;
attribute boolean? pref;
[ChromeOnly]
void initialize(optional sequence<DOMString>? type,
optional DOMString? streetAddress,
optional DOMString? locality,
optional DOMString? region,
optional DOMString? postalCode,
optional DOMString? countryName,
optional boolean? pref);
object toJSON();
};
dictionary ContactAddressInit {
dictionary ContactAddress {
sequence<DOMString>? type;
DOMString? streetAddress;
DOMString? locality;
@ -36,46 +14,16 @@ dictionary ContactAddressInit {
boolean? pref;
};
[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactField;1"]
interface ContactField {
attribute object? type; // DOMString[]
attribute DOMString? value;
attribute boolean? pref;
[ChromeOnly]
void initialize(optional sequence<DOMString>? type,
optional DOMString? value,
optional boolean? pref);
object toJSON();
};
dictionary ContactFieldInit {
dictionary ContactField {
sequence<DOMString>? type;
DOMString? value;
boolean? pref;
};
[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactTelField;1"]
interface ContactTelField : ContactField {
attribute DOMString? carrier;
[ChromeOnly]
void initialize(optional sequence<DOMString>? type,
optional DOMString? value,
optional DOMString? carrier,
optional boolean? pref);
object toJSON();
};
dictionary ContactTelFieldInit : ContactFieldInit {
dictionary ContactTelField : ContactField {
DOMString? carrier;
};
dictionary ContactProperties {
Date? bday;
Date? anniversary;
@ -85,13 +33,13 @@ dictionary ContactProperties {
sequence<Blob>? photo;
sequence<ContactAddressInit>? adr;
sequence<ContactAddress>? adr;
sequence<ContactFieldInit>? email;
sequence<ContactFieldInit>? url;
sequence<ContactFieldInit>? impp;
sequence<ContactField>? email;
sequence<ContactField>? url;
sequence<ContactField>? impp;
sequence<ContactTelFieldInit>? tel;
sequence<ContactTelField>? tel;
sequence<DOMString>? name;
sequence<DOMString>? honorificPrefix;
@ -110,43 +58,43 @@ dictionary ContactProperties {
[Constructor(optional ContactProperties properties),
JSImplementation="@mozilla.org/contact;1"]
interface mozContact {
attribute DOMString id;
readonly attribute Date? published;
readonly attribute Date? updated;
attribute DOMString id;
readonly attribute Date? published;
readonly attribute Date? updated;
attribute Date? bday;
attribute Date? anniversary;
attribute Date? bday;
attribute Date? anniversary;
attribute DOMString? sex;
attribute DOMString? genderIdentity;
attribute DOMString? sex;
attribute DOMString? genderIdentity;
attribute object? photo;
[Cached, Pure] attribute sequence<Blob>? photo;
attribute object? adr;
[Cached, Pure] attribute sequence<ContactAddress>? adr;
attribute object? email;
attribute object? url;
attribute object? impp;
[Cached, Pure] attribute sequence<ContactField>? email;
[Cached, Pure] attribute sequence<ContactField>? url;
[Cached, Pure] attribute sequence<ContactField>? impp;
attribute object? tel;
[Cached, Pure] attribute sequence<ContactTelField>? tel;
attribute object? name;
attribute object? honorificPrefix;
attribute object? givenName;
attribute object? additionalName;
attribute object? familyName;
attribute object? honorificSuffix;
attribute object? nickname;
attribute object? category;
attribute object? org;
attribute object? jobTitle;
attribute object? note;
attribute object? key;
[Cached, Pure] attribute sequence<DOMString>? name;
[Cached, Pure] attribute sequence<DOMString>? honorificPrefix;
[Cached, Pure] attribute sequence<DOMString>? givenName;
[Cached, Pure] attribute sequence<DOMString>? additionalName;
[Cached, Pure] attribute sequence<DOMString>? familyName;
[Cached, Pure] attribute sequence<DOMString>? honorificSuffix;
[Cached, Pure] attribute sequence<DOMString>? nickname;
[Cached, Pure] attribute sequence<DOMString>? category;
[Cached, Pure] attribute sequence<DOMString>? org;
[Cached, Pure] attribute sequence<DOMString>? jobTitle;
[Cached, Pure] attribute sequence<DOMString>? note;
[Cached, Pure] attribute sequence<DOMString>? key;
[ChromeOnly]
void setMetadata(DOMString id, Date? published, Date? updated);
object toJSON();
jsonifier;
};
dictionary ContactFindSortOptions {

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

@ -637,6 +637,7 @@ public class Tab {
clearFavicon();
setHasFeeds(false);
setHasOpenSearch(false);
updateTitle(null);
updateIdentityData(null);
setReaderEnabled(false);

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

@ -18,8 +18,7 @@
android:title="@string/contextmenu_subscribe"/>
<item android:id="@+id/add_search_engine"
android:title="@string/contextmenu_add_search_engine"
android:visible="false"/>
android:title="@string/contextmenu_add_search_engine"/>
<item android:id="@+id/copyurl"
android:title="@string/contextmenu_copyurl"/>

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

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html>
<head id="linkparent">
<title>Autodiscovery Test</title>
</head>
<body>
</body>
</html>

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

@ -67,6 +67,7 @@ skip-if = processor == "x86"
# [testVkbOverlap] # see bug 907274
# Using JavascriptTest
[testBrowserDiscovery]
[testDeviceSearchEngine]
[testJNI]
# [testMozPay] # see bug 945675

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

@ -0,0 +1,10 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.*;
public class testBrowserDiscovery extends JavascriptTest {
public testBrowserDiscovery() {
super("testBrowserDiscovery.js");
}
}

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

@ -0,0 +1,151 @@
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
function ok(passed, text) {
do_report_result(passed, text, Components.stack.caller, false);
}
// We use a global variable to track the <browser> where the tests are happening
let browser;
function setHandlerFunc(handler, test) {
browser.addEventListener("DOMLinkAdded", function linkAdded(event) {
browser.removeEventListener("DOMLinkAdded", linkAdded, false);
Services.tm.mainThread.dispatch(handler.bind(this, test), Ci.nsIThread.DISPATCH_NORMAL);
}, false);
}
add_test(function setup_browser() {
let BrowserApp = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
do_register_cleanup(function cleanup() {
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
});
let url = "http://mochi.test:8888/tests/robocop/link_discovery.html";
browser = BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
browser.addEventListener("load", function startTests(event) {
browser.removeEventListener("load", startTests, true);
Services.tm.mainThread.dispatch(run_next_test, Ci.nsIThread.DISPATCH_NORMAL);
}, true);
});
let searchDiscoveryTests = [
{ text: "rel search discovered" },
{ rel: "SEARCH", text: "rel is case insensitive" },
{ rel: "-search-", pass: false, text: "rel -search- not discovered" },
{ rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
{ href: "https://not.mozilla.com", text: "HTTPS ok" },
{ href: "ftp://not.mozilla.com", text: "FTP ok" },
{ href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
{ href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
{ type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" },
{ type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" },
{ type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
{ type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" },
{ rel: "search search search", count: 1, text: "only one engine should be added" }
];
function execute_search_test(test) {
if (browser.engines) {
let matchCount = (!("count" in test) || browser.engines.length === test.count);
let matchTitle = (test.title == browser.engines[0].title);
ok(matchCount && matchTitle, test.text);
browser.engines = null;
} else {
ok(!test.pass, test.text);
}
run_next_test();
}
function prep_search_test(test) {
setHandlerFunc(execute_search_test, test);
let rel = test.rel || "search";
let href = test.href || "http://so.not.here.mozilla.com/search.xml";
let type = test.type || "application/opensearchdescription+xml";
let title = test.title;
if (!("pass" in test)) {
test.pass = true;
}
let head = browser.contentDocument.getElementById("linkparent");
let link = browser.contentDocument.createElement("link");
link.rel = rel;
link.href = href;
link.type = type;
link.title = title;
head.appendChild(link);
}
let feedDiscoveryTests = [
{ text: "rel feed discovered" },
{ rel: "ALTERNATE", text: "rel is case insensitive" },
{ rel: "-alternate-", pass: false, text: "rel -alternate- not discovered" },
{ rel: "foo bar baz alternate quux", text: "rel may contain additional rels separated by spaces" },
{ href: "https://not.mozilla.com", text: "HTTPS ok" },
{ href: "ftp://not.mozilla.com", text: "FTP ok" },
{ href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
{ href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
{ type: "application/rss+xml", text: "type can be RSS" },
{ type: "aPPliCAtion/RSS+xml", text: "type is case insensitve" },
{ type: " application/atom+xml ", text: "type may contain extra whitespace" },
{ type: "application/atom+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
{ type: "aapplication/atom+xml", pass: false, text: "type should not be loosely matched" },
{ rel: "alternate alternate alternate", count: 1, text: "only one feed should be added" }
];
function execute_feed_test(test) {
if (browser.feeds) {
let matchCount = (!("count" in test) || browser.feeds.length === test.count);
let matchTitle = (test.title == browser.feeds[0].title);
ok(matchCount && matchTitle, test.text);
browser.feeds = null;
} else {
ok(!test.pass, test.text);
}
run_next_test();
}
function prep_feed_test(test) {
setHandlerFunc(execute_feed_test, test);
let rel = test.rel || "alternate";
let href = test.href || "http://so.not.here.mozilla.com/feed.xml";
let type = test.type || "application/atom+xml";
let title = test.title;
if (!("pass" in test)) {
test.pass = true;
}
let head = browser.contentDocument.getElementById("linkparent");
let link = browser.contentDocument.createElement("link");
link.rel = rel;
link.href = href;
link.type = type;
link.title = title;
head.appendChild(link);
}
let searchTest;
while ((searchTest = searchDiscoveryTests.shift())) {
let title = searchTest.title || searchDiscoveryTests.length;
searchTest.title = title;
add_test(prep_search_test.bind(this, searchTest));
}
let feedTest;
while ((feedTest = feedDiscoveryTests.shift())) {
let title = feedTest.title || feedDiscoveryTests.length;
feedTest.title = title;
add_test(prep_feed_test.bind(this, feedTest));
}
run_next_test();

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

@ -365,10 +365,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
menu.findItem(R.id.add_to_launcher).setVisible(false);
}
if (!tab.hasFeeds()) {
menu.findItem(R.id.subscribe).setVisible(false);
}
menu.findItem(R.id.subscribe).setVisible(tab.hasFeeds());
menu.findItem(R.id.add_search_engine).setVisible(tab.hasOpenSearch());
} else {
// if there is no tab, remove anything tab dependent

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

@ -3777,18 +3777,8 @@ Tab.prototype = {
// Clear page-specific opensearch engines and feeds for a new request.
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START && aRequest && aWebProgress.isTopLevel) {
this.browser.engines = null;
// Send message to clear search engine option in context menu.
let newEngineMessage = {
type: "Link:OpenSearch",
tabID: this.id,
visible: false
};
sendMessageToJava(newEngineMessage);
this.browser.feeds = null;
this.browser.engines = null;
this.browser.feeds = null;
}
// true if the page loaded successfully (i.e., no 404s or other errors)