Merge fx-team to m-c a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2015-04-20 17:04:09 -07:00
Родитель aa6cf3235f 689f5b0c2e
Коммит 1d3fd84d27
33 изменённых файлов: 396 добавлений и 400 удалений

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

@ -253,7 +253,9 @@ loop.conversationViews = (function(mozL10n) {
onClick: this._handleDecline},
mozL10n.get("incoming_call_cancel_button")
),
React.createElement("div", {className: "btn-chevron", onClick: this.toggleDropdownMenu})
React.createElement("div", {className: "btn-chevron",
onClick: this.toggleDropdownMenu,
ref: "menu-button"})
),
React.createElement("ul", {className: dropdownMenuClassesDecline},

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

@ -253,7 +253,9 @@ loop.conversationViews = (function(mozL10n) {
onClick={this._handleDecline}>
{mozL10n.get("incoming_call_cancel_button")}
</button>
<div className="btn-chevron" onClick={this.toggleDropdownMenu} />
<div className="btn-chevron"
onClick={this.toggleDropdownMenu}
ref="menu-button" />
</div>
<ul className={dropdownMenuClassesDecline}>

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

@ -163,7 +163,7 @@ loop.panel = (function(_, mozL10n) {
return (
React.createElement("div", {className: "dropdown"},
React.createElement("p", {className: "dnd-status", onClick: this.showDropdownMenu},
React.createElement("p", {className: "dnd-status", onClick: this.toggleDropdownMenu, ref: "menu-button"},
React.createElement("span", null, availabilityText),
React.createElement("i", {className: availabilityStatus})
),
@ -344,8 +344,10 @@ loop.panel = (function(_, mozL10n) {
return (
React.createElement("div", {className: "settings-menu dropdown"},
React.createElement("a", {className: "button-settings", onClick: this.showDropdownMenu,
title: mozL10n.get("settings_menu_button_tooltip")}),
React.createElement("a", {className: "button-settings",
onClick: this.toggleDropdownMenu,
title: mozL10n.get("settings_menu_button_tooltip"),
ref: "menu-button"}),
React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu})},
React.createElement(SettingsDropdownEntry, {label: mozL10n.get("settings_menu_item_settings"),
onClick: this.handleClickSettingsEntry,

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

@ -163,7 +163,7 @@ loop.panel = (function(_, mozL10n) {
return (
<div className="dropdown">
<p className="dnd-status" onClick={this.showDropdownMenu}>
<p className="dnd-status" onClick={this.toggleDropdownMenu} ref="menu-button">
<span>{availabilityText}</span>
<i className={availabilityStatus}></i>
</p>
@ -344,8 +344,10 @@ loop.panel = (function(_, mozL10n) {
return (
<div className="settings-menu dropdown">
<a className="button-settings" onClick={this.showDropdownMenu}
title={mozL10n.get("settings_menu_button_tooltip")} />
<a className="button-settings"
onClick={this.toggleDropdownMenu}
title={mozL10n.get("settings_menu_button_tooltip")}
ref="menu-button" />
<ul className={cx({"dropdown-menu": true, hide: !this.state.showMenu})}>
<SettingsDropdownEntry label={mozL10n.get("settings_menu_item_settings")}
onClick={this.handleClickSettingsEntry}

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

@ -94,8 +94,16 @@ loop.shared.mixins = (function() {
return {showMenu: false};
},
_onBodyClick: function() {
this.setState({showMenu: false});
_onBodyClick: function(event) {
var menuButton = this.refs["menu-button"] && this.refs["menu-button"].getDOMNode();
if (this.refs.anchor) {
menuButton = this.refs.anchor.getDOMNode();
}
// If a menu button/ anchor is defined and clicked on, it will be in charge
// of hiding or showing the popup.
if (event.target !== menuButton) {
this.setState({ showMenu: false });
}
},
_correctMenuPosition: function() {

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

@ -163,6 +163,7 @@ loop.shared.views = (function(_, l10n) {
React.createElement("div", null,
React.createElement("button", {className: screenShareClasses,
onClick: this.handleClick,
ref: "menu-button",
title: this._getTitle()},
isActive ? null : React.createElement("span", {className: "chevron"})
),

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

@ -163,6 +163,7 @@ loop.shared.views = (function(_, l10n) {
<div>
<button className={screenShareClasses}
onClick={this.handleClick}
ref="menu-button"
title={this._getTitle()}>
{isActive ? null : <span className="chevron"/>}
</button>

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

@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@ -42,29 +43,24 @@ function chromeTimeToDate(aTime)
/**
* Insert bookmark items into specific folder.
*
* @param aFolderId
* id of folder where items will be inserted
* @param aItems
* @param parentGuid
* GUID of the folder where items will be inserted
* @param items
* bookmark items to be inserted
*/
function insertBookmarkItems(aFolderId, aItems)
{
for (let i = 0; i < aItems.length; i++) {
let item = aItems[i];
function* insertBookmarkItems(parentGuid, items) {
for (let item of items) {
try {
if (item.type == "url") {
PlacesUtils.bookmarks.insertBookmark(aFolderId,
NetUtil.newURI(item.url),
PlacesUtils.bookmarks.DEFAULT_INDEX,
item.name);
yield PlacesUtils.bookmarks.insert({
parentGuid, url: item.url, title: item.name
});
} else if (item.type == "folder") {
let newFolderId =
PlacesUtils.bookmarks.createFolder(aFolderId,
item.name,
PlacesUtils.bookmarks.DEFAULT_INDEX);
let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title: item.name
})).guid;
insertBookmarkItems(newFolderId, item.children);
yield insertBookmarkItems(newFolderGuid, item.children);
}
} catch (e) {
Cu.reportError(e);
@ -189,48 +185,51 @@ function GetBookmarksResource(aProfileFolder) {
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate: function(aCallback) {
NetUtil.asyncFetch2(bookmarksFile, MigrationUtils.wrapMigrateFunction(
function(aInputStream, aResultCode) {
if (!Components.isSuccessCode(aResultCode))
throw new Error("Could not read Bookmarks file");
// Parse Chrome bookmark file that is JSON format
let bookmarkJSON = NetUtil.readInputStreamToString(
aInputStream, aInputStream.available(), { charset : "UTF-8" });
let roots = JSON.parse(bookmarkJSON).roots;
PlacesUtils.bookmarks.runInBatchMode({
runBatched: function() {
// Importing bookmark bar items
if (roots.bookmark_bar.children &&
roots.bookmark_bar.children.length > 0) {
// Toolbar
let parentId = PlacesUtils.toolbarFolderId;
if (!MigrationUtils.isStartupMigration) {
parentId = MigrationUtils.createImportedBookmarksFolder(
"Chrome", parentId);
}
insertBookmarkItems(parentId, roots.bookmark_bar.children);
}
return Task.spawn(function* () {
let jsonStream = yield new Promise(resolve =>
NetUtil.asyncFetch({ uri: NetUtil.newURI(bookmarksFile),
loadUsingSystemPrincipal: true
},
(inputStream, resultCode) => {
if (Components.isSuccessCode(resultCode)) {
resolve(inputStream);
} else {
reject(new Error("Could not read Bookmarks file"));
}
}
)
);
// Importing bookmark menu items
if (roots.other.children &&
roots.other.children.length > 0) {
// Bookmark menu
let parentId = PlacesUtils.bookmarksMenuFolderId;
if (!MigrationUtils.isStartupMigration) {
parentId = MigrationUtils.createImportedBookmarksFolder(
"Chrome", parentId);
}
insertBookmarkItems(parentId, roots.other.children);
}
}
}, null);
}, aCallback),
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
// Parse Chrome bookmark file that is JSON format
let bookmarkJSON = NetUtil.readInputStreamToString(
jsonStream, jsonStream.available(), { charset : "UTF-8" });
let roots = JSON.parse(bookmarkJSON).roots;
// Importing bookmark bar items
if (roots.bookmark_bar.children &&
roots.bookmark_bar.children.length > 0) {
// Toolbar
let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
if (!MigrationUtils.isStartupMigration) {
parentGuid =
yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
}
yield insertBookmarkItems(parentGuid, roots.bookmark_bar.children);
}
// Importing bookmark menu items
if (roots.other.children &&
roots.other.children.length > 0) {
// Bookmark menu
let parentGuid = PlacesUtils.bookmarks.menuGuid;
if (!MigrationUtils.isStartupMigration) {
parentGuid =
yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
}
yield insertBookmarkItems(parentGuid, roots.other.children);
}
}.bind(this)).then(() => aCallback(true),
e => { Cu.reportError(e); aCallback(false) });
}
};
}

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

@ -13,7 +13,7 @@ const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@ -169,23 +169,19 @@ Bookmarks.prototype = {
},
migrate: function B_migrate(aCallback) {
PlacesUtils.bookmarks.runInBatchMode({
runBatched: (function migrateBatched() {
// Import to the bookmarks menu.
let destFolderId = PlacesUtils.bookmarksMenuFolderId;
if (!MigrationUtils.isStartupMigration) {
destFolderId =
MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
}
this._migrateFolder(this._favoritesFolder, destFolderId);
aCallback(true);
}).bind(this)
}, null);
return Task.spawn(function* () {
// Import to the bookmarks menu.
let folderGuid = PlacesUtils.bookmarks.menuGuid;
if (!MigrationUtils.isStartupMigration) {
folderGuid =
yield MigrationUtils.createImportedBookmarksFolder("IE", folderGuid);
}
yield this._migrateFolder(this._favoritesFolder, folderGuid);
}.bind(this)).then(() => aCallback(true),
e => { Cu.reportError(e); aCallback(false) });
},
_migrateFolder: function B__migrateFolder(aSourceFolder, aDestFolderId) {
_migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) {
// TODO (bug 741993): the favorites order is stored in the Registry, at
// HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
// Until we support it, bookmarks are imported in alphabetical order.
@ -198,26 +194,28 @@ Bookmarks.prototype = {
// Don't use isSymlink(), since it would throw for invalid
// lnk files pointing to URLs or to unresolvable paths.
if (entry.path == entry.target && entry.isDirectory()) {
let destFolderId;
let folderGuid;
if (entry.leafName == this._toolbarFolderName &&
entry.parent.equals(this._favoritesFolder)) {
// Import to the bookmarks toolbar.
destFolderId = PlacesUtils.toolbarFolderId;
folderGuid = PlacesUtils.bookmarks.toolbarGuid;
if (!MigrationUtils.isStartupMigration) {
destFolderId =
MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
folderGuid =
yield MigrationUtils.createImportedBookmarksFolder("IE", folderGuid);
}
}
else {
// Import to a new folder.
destFolderId =
PlacesUtils.bookmarks.createFolder(aDestFolderId, entry.leafName,
PlacesUtils.bookmarks.DEFAULT_INDEX);
folderGuid = (yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: aDestFolderGuid,
title: entry.leafName
})).guid;
}
if (entry.isReadable()) {
// Recursively import the folder.
this._migrateFolder(entry, destFolderId);
yield this._migrateFolder(entry, folderGuid);
}
}
else {
@ -230,17 +228,16 @@ Bookmarks.prototype = {
let uri = fileHandler.readURLFile(entry);
let title = matches[1];
PlacesUtils.bookmarks.insertBookmark(aDestFolderId,
uri,
PlacesUtils.bookmarks.DEFAULT_INDEX,
title);
yield PlacesUtils.bookmarks.insert({
parentGuid: aDestFolderGuid, url: uri, title
});
}
}
} catch (ex) {
Components.utils.reportError("Unable to import IE favorite (" + entry.leafName + "): " + ex);
}
}
}
})
};
function History() {

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

@ -15,11 +15,10 @@ const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmar
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
"resource://gre/modules/BookmarkHTMLUtils.jsm");
@ -422,23 +421,22 @@ this.MigrationUtils = Object.freeze({
* Helper for creating a folder for imported bookmarks from a particular
* migration source. The folder is created at the end of the given folder.
*
* @param aSourceNameStr
* @param sourceNameStr
* the source name (first letter capitalized). This is used
* for reading the localized source name from the migration
* bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
* sourceNameMosaic from the migration bundle).
* @param aParentId
* the item-id of the folder in which the new folder should be
* created.
* @return the item-id of the new folder.
* @param parentGuid
* the GUID of the folder in which the new folder should be created.
* @return the GUID of the new folder.
*/
createImportedBookmarksFolder:
function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) {
let source = this.getLocalizedString("sourceName" + aSourceNameStr);
let label = this.getLocalizedString("importedBookmarksFolder", [source]);
return PlacesUtils.bookmarks.createFolder(
aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX);
},
createImportedBookmarksFolder: Task.async(function* (sourceNameStr, parentGuid) {
let source = this.getLocalizedString("sourceName" + sourceNameStr);
let title = this.getLocalizedString("importedBookmarksFolder", [source]);
return (yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title
})).guid;
}),
get _migrators() {
return gMigrators ? gMigrators : gMigrators = new Map();

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

@ -32,23 +32,21 @@ Bookmarks.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate: function B_migrate(aCallback) {
PropertyListUtils.read(this._file,
MigrationUtils.wrapMigrateFunction(function migrateBookmarks(aDict) {
if (!aDict)
throw new Error("Could not read Bookmarks.plist");
return Task.spawn(function* () {
let dict = yield new Promise(resolve =>
PropertyListUtils.read(this._file, resolve)
);
if (!dict)
throw new Error("Could not read Bookmarks.plist");
let children = dict.get("Children");
if (!children)
throw new Error("Invalid Bookmarks.plist format");
let children = aDict.get("Children");;
if (!children)
throw new Error("Invalid Bookmarks.plist format");
PlacesUtils.bookmarks.runInBatchMode({
runBatched: function() {
let collection = aDict.get("Title") == "com.apple.ReadingList" ?
this.READING_LIST_COLLECTION : this.ROOT_COLLECTION;
this._migrateCollection(children, collection);
}.bind(this)
}, null);
}.bind(this), aCallback));
let collection = dict.get("Title") == "com.apple.ReadingList" ?
this.READING_LIST_COLLECTION : this.ROOT_COLLECTION;
yield this._migrateCollection(children, collection);
}.bind(this)).then(() => aCallback(true),
e => { Cu.reportError(e); aCallback(false) });
},
// Bookmarks collections in Safari. Constants for migrateCollection.
@ -65,7 +63,7 @@ Bookmarks.prototype = {
* @param aCollection
* one of the values above.
*/
_migrateCollection: function B__migrateCollection(aEntries, aCollection) {
_migrateCollection: Task.async(function* (aEntries, aCollection) {
// A collection of bookmarks in Safari resembles places roots. In the
// property list files (Bookmarks.plist, ReadingList.plist) they are
// stored as regular bookmarks folders, and thus can only be distinguished
@ -79,11 +77,11 @@ Bookmarks.prototype = {
let title = entry.get("Title");
let children = entry.get("Children");
if (title == "BookmarksBar")
this._migrateCollection(children, this.TOOLBAR_COLLECTION);
yield this._migrateCollection(children, this.TOOLBAR_COLLECTION);
else if (title == "BookmarksMenu")
this._migrateCollection(children, this.MENU_COLLECTION);
yield this._migrateCollection(children, this.MENU_COLLECTION);
else if (title == "com.apple.ReadingList")
this._migrateCollection(children, this.READING_LIST_COLLECTION);
yield this._migrateCollection(children, this.READING_LIST_COLLECTION);
else if (entry.get("ShouldOmitFromUI") !== true)
entriesFiltered.push(entry);
}
@ -99,7 +97,7 @@ Bookmarks.prototype = {
if (entriesFiltered.length == 0)
return;
let folder = -1;
let folderGuid = -1;
switch (aCollection) {
case this.ROOT_COLLECTION: {
// In Safari, it is possible (though quite cumbersome) to move
@ -108,22 +106,22 @@ Bookmarks.prototype = {
// both the places root and the unfiled-bookmarks root.
// Because the former is only an implementation detail in our UI,
// the unfiled root seems to be the best choice.
folder = PlacesUtils.unfiledBookmarksFolderId;
folderGuid = PlacesUtils.bookmarks.unfiledGuid;
break;
}
case this.MENU_COLLECTION: {
folder = PlacesUtils.bookmarksMenuFolderId;
folderGuid = PlacesUtils.bookmarks.menuGuid;
if (!MigrationUtils.isStartupMigration) {
folder = MigrationUtils.createImportedBookmarksFolder("Safari",
folder);
folderGuid =
yield MigrationUtils.createImportedBookmarksFolder("Safari", folderGuid);
}
break;
}
case this.TOOLBAR_COLLECTION: {
folder = PlacesUtils.toolbarFolderId;
folderGuid = PlacesUtils.bookmarks.toolbarGuid;
if (!MigrationUtils.isStartupMigration) {
folder = MigrationUtils.createImportedBookmarksFolder("Safari",
folder);
folderGuid =
yield MigrationUtils.createImportedBookmarksFolder("Safari", folderGuid);
}
break;
}
@ -131,51 +129,52 @@ Bookmarks.prototype = {
// Reading list items are imported as regular bookmarks.
// They are imported under their own folder, created either under the
// bookmarks menu (in the case of startup migration).
folder = PlacesUtils.bookmarks.createFolder(
PlacesUtils.bookmarksMenuFolderId,
MigrationUtils.getLocalizedString("importedSafariReadingList"),
PlacesUtils.bookmarks.DEFAULT_INDEX);
folderGuid = (yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: MigrationUtils.getLocalizedString("importedSafariReadingList"),
})).guid;
break;
}
default:
throw new Error("Unexpected value for aCollection!");
}
if (folderGuid == -1)
throw new Error("Invalid folder GUID");
this._migrateEntries(entriesFiltered, folder);
},
yield this._migrateEntries(entriesFiltered, folderGuid);
}),
// migrate the given array of safari bookmarks to the given places
// folder.
_migrateEntries: function B__migrateEntries(aEntries, aFolderId) {
for (let entry of aEntries) {
_migrateEntries: Task.async(function* (entries, parentGuid) {
for (let entry of entries) {
let type = entry.get("WebBookmarkType");
if (type == "WebBookmarkTypeList" && entry.has("Children")) {
let title = entry.get("Title");
let folderId = PlacesUtils.bookmarks.createFolder(
aFolderId, title, PlacesUtils.bookmarks.DEFAULT_INDEX);
let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title
})).guid;
// Empty folders may not have a children array.
if (entry.has("Children"))
this._migrateEntries(entry.get("Children"), folderId, false);
yield this._migrateEntries(entry.get("Children"), newFolderGuid, false);
}
else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
let title, uri;
let title;
if (entry.has("URIDictionary"))
title = entry.get("URIDictionary").get("title");
try {
uri = NetUtil.newURI(entry.get("URLString"));
}
catch(ex) {
Cu.reportError("Invalid uri set for Safari bookmark: " + entry.get("URLString"));
}
if (uri) {
PlacesUtils.bookmarks.insertBookmark(aFolderId, uri,
PlacesUtils.bookmarks.DEFAULT_INDEX, title);
yield PlacesUtils.bookmarks.insert({
parentGuid, url: entry.get("URLString"), title
});
} catch(ex) {
Cu.reportError("Invalid Safari bookmark: " + ex);
}
}
}
}
})
};
function History(aHistoryFile) {

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

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

@ -35,3 +35,26 @@ function promiseMigration(migrator, resourceType) {
migrator.migrate(resourceType, null, null);
});
}
/**
* Replaces a directory service entry with a given nsIFile.
*/
function registerFakePath(key, file) {
// Register our own provider for the Library directory.
let provider = {
getFile(prop, persistent) {
persistent.value = true;
if (prop == key) {
return file;
}
throw Cr.NS_ERROR_FAILURE;
},
QueryInterface: XPCOMUtils.generateQI([ Ci.nsIDirectoryServiceProvider ])
};
Services.dirsvc.QueryInterface(Ci.nsIDirectoryService)
.registerProvider(provider);
do_register_cleanup(() => {
Services.dirsvc.QueryInterface(Ci.nsIDirectoryService)
.unregisterProvider(provider);
});
}

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

@ -0,0 +1,37 @@
add_task(function* () {
registerFakePath("ULibDir", do_get_file("Library/"));
let migrator = MigrationUtils.getMigrator("safari");
// Sanity check for the source.
Assert.ok(migrator.sourceExists);
// Wait for the imported bookmarks. Check that "From Safari"
// folders are created on the toolbar.
let source = MigrationUtils.getLocalizedString("sourceNameSafari");
let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
let expectedParents = [ PlacesUtils.toolbarFolderId ];
PlacesUtils.bookmarks.addObserver({
onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
if (aTitle == label) {
let index = expectedParents.indexOf(aParentId);
Assert.notEqual(index, -1);
expectedParents.splice(index, 1);
if (expectedParents.length == 0)
PlacesUtils.bookmarks.removeObserver(this);
}
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemRemoved() {},
onItemChanged() {},
onItemVisited() {},
onItemMoved() {},
}, false);
yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
// Check the bookmarks have been imported to all the expected parents.
Assert.equal(expectedParents.length, 0);
});

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

@ -3,9 +3,13 @@ head = head_migration.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
Library/Safari/Bookmarks.plist
[test_fx_fhr.js]
[test_IE_bookmarks.js]
skip-if = os != "win"
[test_IE_cookies.js]
skip-if = os != "win"
[test_Safari_bookmarks.js]
skip-if = os != "mac"

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

@ -220,7 +220,13 @@ let SessionFileInternal = {
break;
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
exists = false;
} catch (ex if ex instanceof OS.File.Error) {
// The file might be inaccessible due to wrong permissions
// or similar failures. We'll just count it as "corrupted".
console.error("Could not read session file ", ex, ex.stack);
corrupted = true;
} catch (ex if ex instanceof SyntaxError) {
console.error("Corrupt session file (invalid JSON found) ", ex, ex.stack);
// File is corrupted, try next file
corrupted = true;
} finally {

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

@ -30,7 +30,6 @@ add_task(function* init() {
});
add_task(function* test_creation() {
let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak");
let OLD_UPGRADE_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak-0000000");
@ -115,6 +114,27 @@ add_task(function* test_recovery() {
yield File.writeAtomic(Paths.recoveryBackup, SOURCE);
yield File.writeAtomic(Paths.recovery, "<Invalid JSON>");
is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
yield SessionFile.wipe();
});
add_task(function* test_recovery_inaccessible() {
// Can't do chmod() on non-UNIX platforms, we need that for this test.
if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") {
return;
}
info("Making recovery file inaccessible, attempting to recover from recovery backup");
let SOURCE_RECOVERY = yield promiseSource("Paths.recovery");
let SOURCE = yield promiseSource("Paths.recoveryBackup");
yield File.makeDir(Paths.backups);
yield File.writeAtomic(Paths.recoveryBackup, SOURCE);
// Write a valid recovery file but make it inaccessible.
yield File.writeAtomic(Paths.recovery, SOURCE_RECOVERY);
yield File.setPermissions(Paths.recovery, { unixMode: 0 });
is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
yield File.setPermissions(Paths.recovery, { unixMode: 0644 });
});
add_task(function* test_clean() {

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

@ -8,14 +8,14 @@ const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for notificat
let test = asyncTest(function* () {
yield loadTab(TEST_URI);
let gotEvents = waitForEvents();
let hud = yield openConsole();
let consoleOpened = promise.defer();
let gotEvents = waitForEvents(consoleOpened.promise);
let hud = yield openConsole().then(() => consoleOpened.resolve());
yield gotEvents;
});
function waitForEvents() {
function waitForEvents(onConsoleOpened) {
let deferred = promise.defer();
function webConsoleCreated(aID)
@ -37,7 +37,7 @@ function waitForEvents() {
Services.obs.removeObserver(observer, "web-console-message-created");
ok(aID, "we have a console ID");
is(typeof aNodeID, "string", "message node id is a string");
executeSoon(closeConsole);
onConsoleOpened.then(closeConsole);
}
let observer = {

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

@ -17,6 +17,7 @@
#include "nsDOMTokenList.h"
#include "nsFocusManager.h"
#include "nsFrame.h"
#include "nsGenericHTMLElement.h"
#include "nsIDocument.h"
#include "nsIDocShell.h"
#include "nsIDOMDocument.h"
@ -622,10 +623,16 @@ SelectionCarets::SelectWord()
}
} else {
nsIContent* focusedContent = GetFocusedContent();
if (focusedContent && focusedContent->GetTextEditorRootContent()) {
nsIDOMWindow* win = mPresShell->GetDocument()->GetWindow();
if (win) {
fm->ClearFocus(win);
if (focusedContent) {
// Clear focus if content was editable element, or contentEditable.
nsGenericHTMLElement* focusedGeneric =
nsGenericHTMLElement::FromContent(focusedContent);
if (focusedContent->GetTextEditorRootContent() ||
(focusedGeneric && focusedGeneric->IsContentEditable())) {
nsIDOMWindow* win = mPresShell->GetDocument()->GetWindow();
if (win) {
fm->ClearFocus(win);
}
}
}
}

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

@ -142,20 +142,23 @@ LogManager.prototype = {
this._prefs = null;
},
get _logFileDirectory() {
// At this point we don't allow a custom directory for the logs so
// about:sync-log can be used. We could fix this later if necessary.
return FileUtils.getDir("ProfD", ["weave", "logs"]);
get _logFileSubDirectoryEntries() {
// At this point we don't allow a custom directory for the logs, nor allow
// it to be outside the profile directory.
// This returns an array of the the relative directory entries below the
// profile dir, and is the directory about:sync-log uses.
return ["weave", "logs"];
},
/**
* Copy an input stream to the named file, doing everything off the main
* thread.
* outputFile is an nsIFile, but is used only for the name.
* Returns a promise that is resolved with the file modification date on
* completion or rejected if there is an error.
* outputFileName is a string with the tail of the filename - the file will
* be created in the log directory.
* Returns a promise that is resolved<undefined> on completion or rejected if
* there is an error.
*/
_copyStreamToFile: Task.async(function* (inputStream, outputFile) {
_copyStreamToFile: Task.async(function* (inputStream, outputFileName) {
// The log data could be large, so we don't want to pass it all in a single
// message, so use BUFFER_SIZE chunks.
const BUFFER_SIZE = 8192;
@ -163,7 +166,11 @@ LogManager.prototype = {
// get a binary stream
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
binaryStream.setInputStream(inputStream);
yield OS.File.makeDir(outputFile.parent.path, { ignoreExisting: true });
// We assume the profile directory exists, but not that the dirs under it do.
let profd = FileUtils.getDir("ProfD", []);
let outputFile = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries);
yield OS.File.makeDir(outputFile.path, { ignoreExisting: true, from: profd.path });
outputFile.append(outputFileName);
let output = yield OS.File.open(outputFile.path, { write: true} );
try {
while (true) {
@ -221,12 +228,10 @@ LogManager.prototype = {
// We have reasonPrefix at the start of the filename so all "error"
// logs are grouped in about:sync-log.
let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
let file = this._logFileDirectory;
file.append(filename);
this._log.trace("Beginning stream copy to " + file.leafName + ": " +
this._log.trace("Beginning stream copy to " + filename + ": " +
Date.now());
try {
yield this._copyStreamToFile(inStream, file);
yield this._copyStreamToFile(inStream, filename);
this._log.trace("onCopyComplete", Date.now());
} catch (ex) {
this._log.error("Failed to copy log stream to file", ex);
@ -256,7 +261,8 @@ LogManager.prototype = {
*/
cleanupLogs: Task.async(function* () {
this._cleaningUpFileLogs = true;
let iterator = new OS.File.DirectoryIterator(this._logFileDirectory.path);
let logDir = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries);
let iterator = new OS.File.DirectoryIterator(logDir.path);
let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE);
let threshold = Date.now() - 1000 * maxAge;

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

@ -6,6 +6,7 @@
Cu.import("resource://services-common/logmanager.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
function run_test() {
run_next_test();
@ -101,3 +102,34 @@ add_task(function* test_SharedLogs() {
lm1.finalize();
lm2.finalize();
});
// A little helper to test what log files exist. We expect exactly zero (if
// prefix is null) or exactly one with the specified prefix.
function checkLogFile(prefix) {
let logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
let entries = logsdir.directoryEntries;
if (!prefix) {
// expecting no files.
ok(!entries.hasMoreElements());
} else {
// expecting 1 file.
ok(entries.hasMoreElements());
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
equal(logfile.leafName.slice(-4), ".txt");
ok(logfile.leafName.startsWith(prefix + "-test-"), logfile.leafName);
// and remove it ready for the next check.
logfile.remove(false);
}
}
// Test that we correctly write error logs by default
add_task(function* test_logFileErrorDefault() {
let lm = new LogManager("log-manager.test.", ["TestLog2"], "test");
let log = Log.repository.getLogger("TestLog2");
log.error("an error message");
yield lm.resetFileLog(lm.REASON_ERROR);
// One error log file exists.
checkLogFile("error");
lm.finalize();
});

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

@ -652,16 +652,18 @@ function EnvironmentCache() {
this._updateSettings();
#ifndef MOZ_WIDGET_ANDROID
this._currentEnvironment.profile = {};
#endif
// Build the remaining asynchronous parts of the environment. Don't register change listeners
// until the initial environment has been built.
this._addonBuilder = new EnvironmentAddonBuilder(this);
this._initTask = Promise.all([this._addonBuilder.init(), this._updateProfile()])
let p = [ this._addonBuilder.init() ];
#ifndef MOZ_WIDGET_ANDROID
this._currentEnvironment.profile = {};
p.push(this._updateProfile());
#endif
this._initTask = Promise.all(p)
.then(
() => {
this._initTask = null;
@ -671,7 +673,7 @@ function EnvironmentCache() {
},
(err) => {
// log errors but eat them for consumers
this._log.error("error while initializing", err);
this._log.error("EnvironmentCache - error while initializing", err);
this._initTask = null;
this._startWatchingPrefs();
this._addonBuilder.watchForChanges();
@ -1019,7 +1021,9 @@ EnvironmentCache.prototype = {
let gfxData = {
D2DEnabled: getGfxField("D2DEnabled", null),
DWriteEnabled: getGfxField("DWriteEnabled", null),
DWriteVersion: getGfxField("DWriteVersion", null),
// The following line is disabled due to main thread jank and will be enabled
// again as part of bug 1154500.
//DWriteVersion: getGfxField("DWriteVersion", null),
adapters: [],
};

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

@ -604,13 +604,16 @@ let Impl = {
", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
let archivePromise = this._archivePing(pingData)
.catch(e => this._log.error("addPendingPing - Failed to archive ping " + pingData.id, e));
let savePromise = TelemetryFile.savePing(pingData, aOptions.overwrite);
let archivePromise = this._archivePing(pingData).catch(e => {
this._log.error("addPendingPing - Failed to archive ping " + pingData.id, e);
});
// Wait for both the archiving and ping persistence to complete.
let promises = [
savePromise,
archivePromise,
TelemetryFile.savePing(pingData, aOptions.overwrite),
];
return Promise.all(promises).then(() => pingData.id);
},
@ -1076,7 +1079,8 @@ let Impl = {
const creationDate = new Date(aPingData.creationDate);
const filePath = getArchivedPingPath(aPingData.id, creationDate, aPingData.type);
yield OS.File.makeDir(OS.Path.dirname(filePath), { ignoreExisting: true });
yield OS.File.makeDir(OS.Path.dirname(filePath), { ignoreExisting: true,
from: OS.Constants.Path.profileDir });
yield TelemetryFile.savePingToFile(aPingData, filePath, true);
}),

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

@ -249,7 +249,8 @@ function toLocalTimeISOString(date) {
}
let sign = (n) => n >= 0 ? "+" : "-";
let tzOffset = date.getTimezoneOffset();
// getTimezoneOffset counter-intuitively returns -60 for UTC+1.
let tzOffset = - date.getTimezoneOffset();
// YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
return padNumber(date.getFullYear(), 4)
@ -259,8 +260,8 @@ function toLocalTimeISOString(date) {
+ ":" + padNumber(date.getMinutes(), 2)
+ ":" + padNumber(date.getSeconds(), 2)
+ "." + date.getMilliseconds()
+ sign(tzOffset) + Math.abs(Math.floor(tzOffset / 60))
+ ":" + Math.abs(tzOffset % 60);
+ sign(tzOffset) + padNumber(Math.floor(Math.abs(tzOffset / 60)), 2)
+ ":" + padNumber(Math.abs(tzOffset % 60), 2);
}
/**
@ -1703,9 +1704,8 @@ let Impl = {
addClientId: true,
addEnvironment: true,
overwrite: true,
filePath: file.path,
};
return TelemetryPing.addPendingPing(getPingType(payload), payload, options);
return TelemetryPing.savePing(getPingType(payload), payload, file.path, options);
},
/**
@ -2071,7 +2071,7 @@ let Impl = {
if (abortedExists) {
this._log.trace("_checkAbortedSessionPing - aborted session found: " + FILE_PATH);
yield this._abortedSessionSerializer.enqueueTask(
() => TelemetryPing.addPendingPing(FILE_PATH, true));
() => TelemetryPing.addPendingPingFromFile(FILE_PATH, true));
}
}),

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

@ -112,7 +112,7 @@ Structure::
gfx: {
D2DEnabled: <bool>, // null on failure
DWriteEnabled: <bool>, // null on failure
DWriteVersion: <string>, // null on failure
//DWriteVersion: <string>, // temporarily removed, pending bug 1154500
adapters: [
{
description: <string>, // e.g. "Intel(R) HD Graphics 4600", null on failure

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

@ -392,11 +392,14 @@ function checkSystemSection(data) {
let gfxData = data.system.gfx;
Assert.ok("D2DEnabled" in gfxData);
Assert.ok("DWriteEnabled" in gfxData);
Assert.ok("DWriteVersion" in gfxData);
// DWriteVersion is disabled due to main thread jank and will be enabled
// again as part of bug 1154500.
//Assert.ok("DWriteVersion" in gfxData);
if (gIsWindows) {
Assert.equal(typeof gfxData.D2DEnabled, "boolean");
Assert.equal(typeof gfxData.DWriteEnabled, "boolean");
Assert.ok(checkString(gfxData.DWriteVersion));
// As above, will be enabled again as part of bug 1154500.
//Assert.ok(checkString(gfxData.DWriteVersion));
}
Assert.ok("adapters" in gfxData);

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

@ -212,15 +212,23 @@ function checkPayloadInfo(data) {
const ALLOWED_REASONS = [
"environment-change", "shutdown", "daily", "saved-session", "test-ping"
];
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
let numberCheck = arg => { return (typeof arg == "number"); };
let positiveNumberCheck = arg => { return numberCheck(arg) && (arg >= 0); };
let stringCheck = arg => { return (typeof arg == "string") && (arg != ""); };
let isoDateCheck = arg => { return stringCheck(arg) && !Number.isNaN(Date.parse(arg)); }
let revisionCheck = arg => {
return (Services.appinfo.isOfficial) ? stringCheck(arg) : (typeof arg == "string");
};
let uuidCheck = arg => uuidRegex.test(arg);
let uuidCheck = arg => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(arg);
};
let isoDateCheck = arg => {
// We expect use of this version of the ISO format:
// 2015-04-12T18:51:19.1+00:00
const isoDateRegEx = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{2}:\d{2}$/;
return stringCheck(arg) && !Number.isNaN(Date.parse(arg)) &&
isoDateRegEx.test(arg);
};
const EXPECTED_INFO_FIELDS_TYPES = {
reason: stringCheck,
@ -1582,6 +1590,7 @@ add_task(function* test_schedulerUserIdle() {
// We should not miss midnight when going to idle.
now.setHours(23);
now.setMinutes(50);
fakeNow(now);
fakeIdleNotification("idle");
Assert.equal(schedulerTimeout, 10 * 60 * 1000);

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

@ -65,11 +65,20 @@ let WebProgressListener = {
_setupJSON: function setupJSON(aWebProgress, aRequest) {
if (aWebProgress) {
let domWindowID;
try {
domWindowID = aWebProgress && aWebProgress.DOMWindowID;
} catch (e) {
// If nsDocShell::Destroy has already been called, then we'll
// get NS_NOINTERFACE when trying to get the DOM window ID.
domWindowID = null;
}
aWebProgress = {
isTopLevel: aWebProgress.isTopLevel,
isLoadingDocument: aWebProgress.isLoadingDocument,
loadType: aWebProgress.loadType,
DOMWindowID: aWebProgress.DOMWindowID,
DOMWindowID: domWindowID
};
}

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

@ -99,67 +99,11 @@ this.ForgetAboutSite = {
}
// Downloads
let useJSTransfer = false;
try {
// This method throws an exception if the old Download Manager is disabled.
Services.downloads.activeDownloadCount;
} catch (ex) {
useJSTransfer = true;
}
if (useJSTransfer) {
Task.spawn(function*() {
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(download => hasRootDomain(
NetUtil.newURI(download.source.url).host, aDomain));
}).then(null, Cu.reportError);
}
else {
let dm = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager);
// Active downloads
for (let enumerator of [dm.activeDownloads, dm.activePrivateDownloads]) {
while (enumerator.hasMoreElements()) {
let dl = enumerator.getNext().QueryInterface(Ci.nsIDownload);
if (hasRootDomain(dl.source.host, aDomain)) {
dl.cancel();
dl.remove();
}
}
const deleteAllLike = function(db) {
// NOTE: This is lossy, but we feel that it is OK to be lossy here and not
// invoke the cost of creating a URI for each download entry and
// ensure that the hostname matches.
let stmt = db.createStatement(
"DELETE FROM moz_downloads " +
"WHERE source LIKE ?1 ESCAPE '/' " +
"AND state NOT IN (?2, ?3, ?4)"
);
let pattern = stmt.escapeStringForLIKE(aDomain, "/");
stmt.bindByIndex(0, "%" + pattern + "%");
stmt.bindByIndex(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
stmt.bindByIndex(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
stmt.bindByIndex(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
try {
stmt.execute();
}
finally {
stmt.finalize();
}
}
// Completed downloads
deleteAllLike(dm.DBConnection);
deleteAllLike(dm.privateDBConnection);
// We want to rebuild the list if the UI is showing, so dispatch the
// observer topic
let os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.notifyObservers(null, "download-manager-remove-download", null);
}
}
Task.spawn(function*() {
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(download => hasRootDomain(
NetUtil.newURI(download.source.url).host, aDomain));
}).then(null, Cu.reportError);
// Passwords
let lm = Cc["@mozilla.org/login-manager;1"].

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

@ -15,7 +15,6 @@ var profileDir = do_get_profile();
function cleanUp()
{
let files = [
"downloads.sqlite",
"places.sqlite",
"cookies.sqlite",
"signons.sqlite",
@ -30,14 +29,3 @@ function cleanUp()
}
}
cleanUp();
function oldDownloadManagerDisabled()
{
try {
// This method throws an exception if the old Download Manager is disabled.
Services.downloads.activeDownloadCount;
} catch (ex) {
return true;
}
return false;
}

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

@ -107,74 +107,6 @@ function check_cookie_exists(aDomain, aExists)
checker(cm.cookieExists(cookie));
}
/**
* Adds a download to download history.
*
* @param aURIString
* The string of the URI to add.
* @param aIsActive
* If it should be set to an active state in the database. This does not
* make it show up in the list of active downloads however!
*/
function add_download(aURIString, aIsActive)
{
function makeGUID() {
let guid = "";
for (var i = 0; i < 12; i++)
guid += Math.floor(Math.random() * 10);
return guid;
}
check_downloaded(aURIString, false);
let db = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager).
DBConnection;
let stmt = db.createStatement(
"INSERT INTO moz_downloads (source, state, guid) " +
"VALUES (:source, :state, :guid)"
);
stmt.params.source = aURIString;
stmt.params.state = aIsActive ? Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING :
Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
stmt.params.guid = makeGUID();
try {
stmt.execute();
}
finally {
stmt.finalize();
}
check_downloaded(aURIString, true);
}
/**
* Checks to ensure a URI string is in download history or not.
*
* @param aURIString
* The string of the URI to check.
* @param aIsDownloaded
* True if the URI should be downloaded, false otherwise.
*/
function check_downloaded(aURIString, aIsDownloaded)
{
let db = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager).
DBConnection;
let stmt = db.createStatement(
"SELECT * " +
"FROM moz_downloads " +
"WHERE source = :source"
);
stmt.params.source = aURIString;
let checker = aIsDownloaded ? do_check_true : do_check_false;
try {
checker(stmt.executeStep());
}
finally {
stmt.finalize();
}
}
/**
* Adds a disabled host to the login manager.
*
@ -380,51 +312,6 @@ function test_cookie_not_cleared_with_uri_contains_domain()
check_cookie_exists(TEST_DOMAIN, true);
}
// Download Manager
function test_download_history_cleared_with_direct_match()
{
if (oldDownloadManagerDisabled()) {
return;
}
const TEST_URI = "http://mozilla.org/foo";
add_download(TEST_URI, false);
ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_downloaded(TEST_URI, false);
}
function test_download_history_cleared_with_subdomain()
{
if (oldDownloadManagerDisabled()) {
return;
}
const TEST_URI = "http://www.mozilla.org/foo";
add_download(TEST_URI, false);
ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_downloaded(TEST_URI, false);
}
function test_download_history_not_cleared_with_active_direct_match()
{
if (oldDownloadManagerDisabled()) {
return;
}
// Tests that downloads marked as active in the db are not deleted from the db
const TEST_URI = "http://mozilla.org/foo";
add_download(TEST_URI, true);
ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_downloaded(TEST_URI, true);
// Reset state
let db = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager).
DBConnection;
db.executeSimpleSQL("DELETE FROM moz_downloads");
check_downloaded(TEST_URI, false);
}
// Login Manager
function test_login_manager_disabled_hosts_cleared_with_direct_match()
{
@ -650,12 +537,6 @@ let tests = [
test_cookie_cleared_with_subdomain,
test_cookie_not_cleared_with_uri_contains_domain,
// Download Manager
// Note: active downloads tested in test_removeDataFromDomain_activeDownloads.js
test_download_history_cleared_with_direct_match,
test_download_history_cleared_with_subdomain,
test_download_history_not_cleared_with_active_direct_match,
// Login Manager
test_login_manager_disabled_hosts_cleared_with_direct_match,
test_login_manager_disabled_hosts_cleared_with_subdomain,

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

@ -807,6 +807,8 @@ function LazyMapProxyHandler () {
}
return target.delete(key);
};
case "has":
return key => target.has(key);
default:
return target[name];
}

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

@ -35,7 +35,13 @@ function checkMainPropertyList(aPropertyListRoot) {
const PRIMITIVE = PropertyListUtils.TYPE_PRIMITIVE;
checkValue(aPropertyListRoot, PropertyListUtils.TYPE_DICTIONARY);
// Check .has()
Assert.ok(aPropertyListRoot.has("Boolean"));
Assert.ok(!aPropertyListRoot.has("Nonexistent"));
checkValue(aPropertyListRoot.get("Boolean"), PRIMITIVE, false);
let array = aPropertyListRoot.get("Array");
checkValue(array, PropertyListUtils.TYPE_ARRAY);
do_check_eq(array.length, 8);