MozReview-Commit-ID: 6UBB4TW6an5
This commit is contained in:
Kartikaya Gupta 2017-04-27 09:31:17 -04:00
Родитель 51510de95b c90904f9bf
Коммит e37b4e9289
822 изменённых файлов: 35558 добавлений и 26361 удалений

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

@ -72,7 +72,8 @@ browser/extensions/pdfjs/content/web**
browser/extensions/pocket/content/panels/js/tmpl.js
browser/extensions/pocket/content/panels/js/vendor/**
browser/locales/**
# vendor library files in activity-stream
# generated or library files in activity-stream
browser/extensions/activity-stream/data/content/activity-stream.bundle.js
browser/extensions/activity-stream/vendor/**
# imported from chromium
browser/extensions/mortar/**

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

@ -13,5 +13,12 @@ module.exports = {
"dump": true,
"Services": true,
"XPCOMUtils": true
},
"rules": {
// Warn about cyclomatic complexity in functions.
"complexity": ["error", 42],
// Maximum depth callbacks can be nested.
"max-nested-callbacks": ["error", 10],
}
};

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

@ -676,7 +676,7 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
}
#endif
mDocument->ContentRemoved(containerElm, textNode);
mDocument->ContentRemoved(textAcc);
continue;
}

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

@ -52,6 +52,18 @@ TreeWalker::
MOZ_COUNT_CTOR(TreeWalker);
}
TreeWalker::
TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode) :
mDoc(aDocument), mContext(nullptr), mAnchorNode(aAnchorNode),
mARIAOwnsIdx(0),
mChildFilter(nsIContent::eSkipPlaceholderContent | nsIContent::eAllChildren),
mFlags(eWalkCache),
mPhase(eAtStart)
{
MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
MOZ_COUNT_CTOR(TreeWalker);
}
TreeWalker::~TreeWalker()
{
MOZ_COUNT_DTOR(TreeWalker);

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

@ -47,6 +47,11 @@ public:
*/
TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = eWalkCache);
/**
* Navigates the accessible children within the anchor node subtree.
*/
TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode);
~TreeWalker();
/**

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

@ -524,7 +524,7 @@ nsAccessibilityService::DeckPanelSwitched(nsIPresShell* aPresShell,
}
#endif
document->ContentRemoved(aDeckNode, panelNode);
document->ContentRemoved(panelNode);
}
if (aCurrentBoxFrame) {
@ -582,26 +582,7 @@ nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
#endif
if (document) {
// Flatten hierarchy may be broken at this point so we cannot get a true
// container by traversing up the DOM tree. Find a parent of first accessible
// from the subtree of the given DOM node, that'll be a container. If no
// accessibles in subtree then we don't care about the change.
Accessible* child = document->GetAccessible(aChildNode);
if (!child) {
Accessible* container = document->GetContainerAccessible(aChildNode);
a11y::TreeWalker walker(container ? container : document, aChildNode,
a11y::TreeWalker::eWalkCache);
child = walker.Next();
}
if (child) {
MOZ_DIAGNOSTIC_ASSERT(child->Parent(), "Unattached accessible from tree");
document->ContentRemoved(child->Parent(), aChildNode);
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree))
logging::AccessibleNNode("real container", child->Parent());
#endif
}
document->ContentRemoved(aChildNode);
}
#ifdef A11Y_LOG

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

@ -1170,10 +1170,7 @@ DocAccessible::ContentRemoved(nsIDocument* aDocument,
// This one and content removal notification from layout may result in
// double processing of same subtrees. If it pops up in profiling, then
// consider reusing a document node cache to reject these notifications early.
Accessible* container = GetAccessibleOrContainer(aContainerNode);
if (container) {
UpdateTreeOnRemoval(container, aChildNode);
}
ContentRemoved(aChildNode);
}
void
@ -1382,7 +1379,7 @@ DocAccessible::RecreateAccessible(nsIContent* aContent)
// should be coalesced with normal show/hide events.
nsIContent* parent = aContent->GetFlattenedTreeParent();
ContentRemoved(parent, aContent);
ContentRemoved(aContent);
ContentInserted(parent, aContent, aContent->GetNextSibling());
}
@ -1972,33 +1969,36 @@ DocAccessible::FireEventsOnInsertion(Accessible* aContainer)
}
void
DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
DocAccessible::ContentRemoved(Accessible* aContent)
{
// If child node is not accessible then look for its accessible children.
Accessible* child = GetAccessible(aChildNode);
MOZ_DIAGNOSTIC_ASSERT(aContent->Parent(), "Unattached accessible from tree");
#ifdef A11Y_LOG
logging::TreeInfo("process content removal", 0,
"container", aContainer, "child", aChildNode);
"container", aContent->Parent(), "child", aContent, nullptr);
#endif
TreeMutation mt(aContainer);
if (child) {
mt.BeforeRemoval(child);
MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
aContainer->RemoveChild(child);
UncacheChildrenInSubtree(child);
TreeMutation mt(aContent->Parent());
mt.BeforeRemoval(aContent);
aContent->Parent()->RemoveChild(aContent);
UncacheChildrenInSubtree(aContent);
mt.Done();
return;
}
}
TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
while (Accessible* child = walker.Next()) {
mt.BeforeRemoval(child);
MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
aContainer->RemoveChild(child);
UncacheChildrenInSubtree(child);
void
DocAccessible::ContentRemoved(nsIContent* aContentNode)
{
// If child node is not accessible then look for its accessible children.
Accessible* acc = GetAccessible(aContentNode);
if (acc) {
ContentRemoved(acc);
}
else {
TreeWalker walker(this, aContentNode);
while (Accessible* acc = walker.Next()) {
ContentRemoved(acc);
}
}
mt.Done();
}
bool
@ -2051,7 +2051,7 @@ DocAccessible::ValidateARIAOwned()
// If DOM node doesn't have a frame anymore then shutdown its accessible.
if (child->Parent() && !child->GetFrame()) {
UpdateTreeOnRemoval(child->Parent(), child->GetContent());
ContentRemoved(child);
children->RemoveElementAt(idx);
idx--;
continue;

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

@ -342,18 +342,10 @@ public:
nsIContent* aEndChildNode);
/**
* Notify the document accessible that content was removed.
* Update the tree on content removal.
*/
void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
{
// Update the whole tree of this document accessible when the container is
// null (document element is removed).
UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
}
void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
{
ContentRemoved(AccessibleOrTrueContainer(aContainerNode), aChildNode);
}
void ContentRemoved(Accessible* aContent);
void ContentRemoved(nsIContent* aContentNode);
/**
* Updates accessible tree when rendered text is changed.
@ -512,11 +504,6 @@ protected:
*/
void ProcessInvalidationList();
/**
* Update the accessible tree for content removal.
*/
void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
/**
* Validates all aria-owns connections and updates the tree accordingly.
*/

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

@ -62,7 +62,7 @@ module.exports = { // eslint-disable-line no-undef
"comma-dangle": ["error", "never"],
"comma-spacing": "error",
"comma-style": ["error", "last"],
"complexity": ["error", 35],
"complexity": ["error", 20],
"consistent-this": "off",
"curly": ["error", "multi-line"],
"default-case": "off",
@ -70,6 +70,7 @@ module.exports = { // eslint-disable-line no-undef
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "off",
"func-call-spacing": "error",
"func-names": "off",
"func-style": "off",
"generator-star": "off",
@ -141,7 +142,6 @@ module.exports = { // eslint-disable-line no-undef
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-space-before-semi": "off",
"no-spaced-func": "error",
"no-sparse-arrays": "error",
"no-sync": "off",
"no-ternary": "off",

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

@ -17,9 +17,6 @@ module.exports = {
// which is a valid use case.
"no-empty": "error",
// No spaces between function name and parentheses
"no-spaced-func": "error",
// Maximum depth callbacks can be nested.
"max-nested-callbacks": ["error", 8],

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

@ -1530,7 +1530,6 @@ pref("dom.ipc.reportProcessHangs", false);
pref("dom.ipc.reportProcessHangs", true);
#endif
pref("browser.reader.detectedFirstArticle", false);
// Don't limit how many nodes we care about on desktop:
pref("reader.parse-node-limit", 0);

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

@ -71,7 +71,7 @@
</hbox>
<hbox id="apply" align="center">
<button id="updateButton" align="start"
label="&update.updateButton.label2;"
label="&update.updateButton.label3;"
accesskey="&update.updateButton.accesskey;"
oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
<spacer flex="1"/>

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

@ -75,7 +75,7 @@ var PointerlockFsWarning = {
if (aOrigin) {
this._origin = aOrigin;
}
let uri = BrowserUtils.makeURI(this._origin);
let uri = Services.io.newURI(this._origin);
let host = null;
try {
host = uri.host;

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

@ -222,7 +222,7 @@ var gPluginHandler = {
// page. That means that we also need to compare the actual locations to
// ensure we aren't getting a message from a Data URI that we're no longer
// looking at.
let receivedURI = BrowserUtils.makeURI(location);
let receivedURI = Services.io.newURI(location);
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
return;
}
@ -318,7 +318,7 @@ var gPluginHandler = {
// page. That means that we also need to compare the actual locations to
// ensure we aren't getting a message from a Data URI that we're no longer
// looking at.
let receivedURI = BrowserUtils.makeURI(location);
let receivedURI = Services.io.newURI(location);
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
return;
}

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

@ -468,7 +468,7 @@ const gStoragePressureObserver = {
Services.prefs.getIntPref("browser.storageManager.pressureNotification.usageThresholdGB");
let msg = "";
let buttons = [];
let usage = parseInt(data);
let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data
let prefStrBundle = document.getElementById("bundle_preferences");
let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
let notificationBox = document.getElementById("high-priority-global-notificationbox");
@ -4088,7 +4088,7 @@ function FillHistoryMenu(aParent) {
let item = existingIndex < children.length ?
children[existingIndex] : document.createElement("menuitem");
let entryURI = BrowserUtils.makeURI(entry.url, entry.charset, null);
let entryURI = Services.io.newURI(entry.url, entry.charset);
item.setAttribute("uri", uri);
item.setAttribute("label", entry.title || uri);
item.setAttribute("index", j);

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

@ -576,7 +576,7 @@ var ClickEventHandler = {
if (docShell.mixedContentChannel) {
const sm = Services.scriptSecurityManager;
try {
let targetURI = BrowserUtils.makeURI(href);
let targetURI = Services.io.newURI(href);
sm.checkSameOriginURI(docshell.mixedContentChannel.URI, targetURI, false);
json.allowMixedContent = true;
} catch (e) {}
@ -692,7 +692,7 @@ var ClickEventHandler = {
// In case of XLink, we don't return the node we got href from since
// callers expect <a>-like elements.
// Note: makeURI() will throw if aUri is not a valid URI.
return [href ? BrowserUtils.makeURI(href, null, baseURI).spec : null, null,
return [href ? Services.io.newURI(href, null, baseURI).spec : null, null,
node && node.ownerDocument.nodePrincipal];
}
};
@ -867,11 +867,9 @@ addMessageListener("ContextMenu:SearchFieldBookmarkData", (message) => {
let charset = node.ownerDocument.characterSet;
let formBaseURI = BrowserUtils.makeURI(node.form.baseURI,
charset);
let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
let formURI = BrowserUtils.makeURI(node.form.getAttribute("action"),
charset,
let formURI = Services.io.newURI(node.form.getAttribute("action"), charset,
formBaseURI);
let spec = formURI.spec;
@ -1462,8 +1460,8 @@ let OfflineApps = {
return null;
try {
var contentURI = BrowserUtils.makeURI(aWindow.location.href, null, null);
return BrowserUtils.makeURI(attr, aWindow.document.characterSet, contentURI);
return Services.io.newURI(attr, aWindow.document.characterSet,
Services.io.newURI(aWindow.location.href));
} catch (e) {
return null;
}

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

@ -1003,7 +1003,7 @@ var RefreshBlocker = {
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIRefreshURI);
let URI = BrowserUtils.makeURI(data.URI, data.originCharset, null);
let URI = Services.io.newURI(data.URI, data.originCharset);
refreshURI.forceRefreshURI(URI, data.delay, true);
}

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

@ -2108,10 +2108,20 @@
break;
default:
getter = () => {
if (AppConstants.NIGHTLY_BUILD) {
let message =
`[bug 1345098] Lazy browser prematurely inserted via '${name}' property access:\n`
console.log(message + new Error().stack);
}
this._insertBrowser(aTab);
return browser[name];
};
setter = (value) => {
if (AppConstants.NIGHTLY_BUILD) {
let message =
`[bug 1345098] Lazy browser prematurely inserted via '${name}' property access:\n`
console.log(message + new Error().stack);
}
this._insertBrowser(aTab);
return browser[name] = value;
};

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

@ -4,7 +4,10 @@
function notifyStoragePressure(usage = 100) {
let notifyPromise = TestUtils.topicObserved("QuotaManager::StoragePressure", () => true);
Services.obs.notifyObservers(null, "QuotaManager::StoragePressure", usage);
let usageWrapper = Cc["@mozilla.org/supports-PRUint64;1"]
.createInstance(Ci.nsISupportsPRUint64);
usageWrapper.data = usage;
Services.obs.notifyObservers(usageWrapper, "QuotaManager::StoragePressure");
return notifyPromise;
}

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

@ -36,10 +36,6 @@ var whitelist = new Set([
// security/manager/pki/resources/content/device_manager.js
{file: "chrome://pippki/content/load_device.xul"},
// browser/modules/ReaderParent.jsm
{file: "chrome://browser/skin/reader-tour.png"},
{file: "chrome://browser/skin/reader-tour@2x.png"},
// Used by setting this url as a pref in about:config
{file: "chrome://browser/content/newtab/alternativeDefaultSites.json"},
@ -222,11 +218,6 @@ var whitelist = new Set([
platforms: ["linux"]},
// Bug 1348559
{file: "chrome://pippki/content/resetpassword.xul"},
// Bug 1344257
{file: "resource://gre-resources/checkmark.svg"},
{file: "resource://gre-resources/indeterminate-checkmark.svg"},
{file: "resource://gre-resources/radio.svg"},
// Bug 1351078
{file: "resource://gre/modules/Battery.jsm"},
// Bug 1351070

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

@ -65,7 +65,7 @@ let whitelist = [
if (!Services.prefs.getBoolPref("full-screen-api.unprefix.enabled")) {
whitelist.push({
sourceName: /res\/(ua|html)\.css$/i,
sourceName: /(?:res|gre-resources)\/(ua|html)\.css$/i,
errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i,
isFromDevTools: false
});

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

@ -4,6 +4,6 @@ module.exports = {
rules: {
// XXX Bug 1326071 - This should be reduced down - probably to 20 or to
// be removed & synced with the mozilla/recommended value.
"complexity": ["error", {"max": 69}],
"complexity": ["error", 61],
}
};

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

@ -475,7 +475,7 @@
<popupnotification id="PanelUI-update-restart-notification"
popupid="update-restart"
label="&updateRestart.header.message;"
label="&updateRestart.header.message2;"
buttonlabel="&updateRestart.acceptButton.label;"
buttonaccesskey="&updateRestart.acceptButton.accesskey;"
closebuttonhidden="true"
@ -499,16 +499,46 @@
position="bottomcenter topright"
noautofocus="true">
<panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView">
<panelview id="appMenu-mainView" class="cui-widget-panelview">
<panelview id="appMenu-mainView" class="cui-widget-panelview PanelUI-subView">
<vbox class="panel-subview-body">
<toolbarbutton id="appMenu-new-window-button"
class="subviewbutton subviewbutton-iconic"
label="&newNavigatorCmd.label;"
key="key_newNavigator"
command="cmd_newNavigator"/>
<toolbarbutton id="appMenu-private-window-button"
class="subviewbutton subviewbutton-iconic"
label="&newPrivateWindow.label;"
key="key_privatebrowsing"
command="Tools:PrivateBrowsing"/>
<toolbarseparator/>
<toolbarbutton id="appMenu-open-file-button"
class="subviewbutton"
label="&openFileCmd.label;"
key="openFileKb"
command="Browser:OpenFile"
/>
<toolbarbutton id="appMenu-save-file-button"
class="subviewbutton"
label="&savePageCmd.label;"
key="key_savePage"
command="Browser:SavePage"
/>
<toolbarbutton id="appMenu-page-setup-button"
class="subviewbutton"
label="&printSetupCmd.label;"
command="cmd_pageSetup"
/>
<toolbarbutton id="appMenu-print-button"
class="subviewbutton subviewbutton-iconic"
label="&printButton.label;"
key="printKb"
#ifdef XP_MACOSX
command="cmd_print"
#else
command="cmd_printPreview"
#endif
/>
</vbox>
</panelview>
</panelmultiview>

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

@ -149,6 +149,9 @@ const PanelUI = {
* @param aEvent the event (if any) that triggers showing the menu.
*/
show(aEvent) {
if (gPhotonStructure) {
this._ensureShortcutsShown();
}
return new Promise(resolve => {
this.ensureReady().then(() => {
if (this.panel.state == "open" ||
@ -870,6 +873,22 @@ const PanelUI = {
return iconAnchor || candidate;
},
_addedShortcuts: false,
_ensureShortcutsShown() {
if (this._addedShortcuts) {
return;
}
this._addedShortcuts = true;
for (let button of this.mainView.querySelectorAll("toolbarbutton[key]")) {
let keyId = button.getAttribute("key");
let key = document.getElementById(keyId);
if (!key) {
continue;
}
button.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(key));
}
},
_notify(status, topic) {
Services.obs.notifyObservers(window, "panelUI-notification-" + topic, status);
}

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

@ -235,7 +235,7 @@ function loadLibraries() {
gLibs.ese = ctypes.open("esent.dll");
gLibs.kernel = ctypes.open("kernel32.dll");
KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
ctypes.winapi_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
declareESEFunctions();
}

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

@ -79,7 +79,7 @@ function CtypesKernelHelpers() {
this._functions.FileTimeToSystemTime =
this._libs.kernel32.declare("FileTimeToSystemTime",
ctypes.default_abi,
ctypes.winapi_abi,
wintypes.BOOL,
this._structs.FILETIME.ptr,
this._structs.SYSTEMTIME.ptr);

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

@ -171,7 +171,7 @@ let eseDBWritingHelpers = {
loadLibraries();
KERNEL.SystemTimeToFileTime = gLibs.kernel.declare("SystemTimeToFileTime",
ctypes.default_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
ctypes.winapi_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
declareESEFunction("CreateDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);

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

@ -22,8 +22,11 @@ add_task(function* () {
_In_ LPCTSTR lpszCookieData
);
*/
// NOTE: Even though MSDN documentation does not indicate a calling convention,
// InternetSetCookieW is declared in SDK headers as __stdcall but is exported
// from wininet.dll without name mangling, so it is effectively winapi_abi
let setIECookie = wininet.declare("InternetSetCookieW",
ctypes.default_abi,
ctypes.winapi_abi,
BOOL,
LPCTSTR,
LPCTSTR,
@ -37,8 +40,11 @@ add_task(function* () {
_Inout_ LPDWORD lpdwSize
);
*/
// NOTE: Even though MSDN documentation does not indicate a calling convention,
// InternetGetCookieW is declared in SDK headers as __stdcall but is exported
// from wininet.dll without name mangling, so it is effectively winapi_abi
let getIECookie = wininet.declare("InternetGetCookieW",
ctypes.default_abi,
ctypes.winapi_abi,
BOOL,
LPCTSTR,
LPCTSTR,

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

@ -4,6 +4,9 @@
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
const storageManagerDisabled = !SpecialPowers.getBoolPref("browser.storageManager.enabled");
const offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
function test() {
waitForExplicitFinish();
open_preferences(runTest);
@ -20,6 +23,22 @@ function checkElements(expectedPane) {
element.id === "drmGroup") {
continue;
}
// The siteDataGroup in the Storage Management project is currently only pref-on on Nightly for testing purpose.
// During the test and the transition period, we have to check the pref to see if the siteDataGroup
// should be hidden always. This would be a bit bothersome, same as the offlineGroup as below.
// However, this checking is necessary to make sure we don't leak the siteDataGroup into beta/release build
if (element.id == "siteDataGroup" && storageManagerDisabled) {
is_element_hidden(element, "Disabled siteDataGroup should be hidden");
continue;
}
// The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
// so during the transition period, we have to check the pref to see if the offlineGroup
// should be hidden always. See the bug 1354530 for the details.
if (element.id == "offlineGroup" && offlineGroupDisabled) {
is_element_hidden(element, "Disabled offlineGroup should be hidden");
continue;
}
let attributeValue = element.getAttribute("data-category");
let suffix = " (id=" + element.id + ")";
if (attributeValue == "pane" + expectedPane) {

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

@ -7,28 +7,21 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm");
function test() {
waitForExplicitFinish();
let prefs = [
"browser.cache.offline.enable",
"browser.cache.disk.enable",
"browser.cache.memory.enable",
];
for (let pref of prefs) {
Services.prefs.setBoolPref(pref, false);
}
// Adding one fake site so that the SiteDataManager would run.
// Otherwise, without any site then it would just return so we would end up in not testing SiteDataManager.
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.foo.com");
Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
registerCleanupFunction(function() {
for (let pref of prefs) {
Services.prefs.clearUserPref(pref);
}
Services.perms.removeFromPrincipal(principal, "persistent-storage");
});
open_preferences(runTest);
SpecialPowers.pushPrefEnv({set: [
["browser.cache.offline.enable", false],
["browser.cache.disk.enable", false],
["browser.cache.memory.enable", false],
["browser.storageManager.enabled", true],
["browser.preferences.offlineGroup.enabled", true]
]}).then(() => open_preferences(runTest));
}
function runTest(win) {
@ -36,7 +29,6 @@ function runTest(win) {
let tab = win.document;
let elements = tab.getElementById("mainPrefPane").children;
let offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
// Test if advanced pane is opened correctly
win.gotoPref("paneAdvanced");
@ -44,13 +36,6 @@ function runTest(win) {
if (element.nodeName == "preferences") {
continue;
}
// The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
// so during the transition period, we have to check the pref to see if the offlineGroup
// should be hidden always. See the bug 1354530 for the details.
if (element.id == "offlineGroup" && offlineGroupDisabled) {
is_element_hidden(element, "Disabled offlineGroup should be hidden");
continue;
}
let attributeValue = element.getAttribute("data-category");
if (attributeValue == "paneAdvanced") {
is_element_visible(element, "Advanced elements should be visible");

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

@ -148,14 +148,15 @@ function gotoPref(aCategory) {
let categories = document.getElementById("categories");
const kDefaultCategoryInternalName = "paneGeneral";
let hash = document.location.hash;
let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
let breakIndex = category.indexOf("-");
// Subcategories allow for selecting smaller sections of the preferences
// until proper search support is enabled (bug 1353954).
let breakIndex = hash.indexOf("-");
let subcategory = breakIndex != -1 && hash.substring(breakIndex + 1);
let subcategory = breakIndex != -1 && category.substring(breakIndex + 1);
if (subcategory) {
hash = hash.substring(0, breakIndex);
category = category.substring(0, breakIndex);
}
let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
category = friendlyPrefCategoryNameToInternalName(category);
// Updating the hash (below) or changing the selected category

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

@ -611,6 +611,7 @@
value="&browserContainersLearnMore.label;"/>
<spacer flex="1"/>
<button id="browserContainersSettings"
class="accessory-button"
label="&browserContainersSettings.label;"
accesskey="&browserContainersSettings.accesskey;"/>
</hbox>

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

@ -7,6 +7,7 @@ registerCleanupFunction(function() {
Services.prefs.clearUserPref("browser.preferences.instantApply");
});
// Test opening to the differerent panes and subcategories in Preferences
add_task(function*() {
let prefs = yield openPreferencesViaOpenPreferencesAPI("panePrivacy");
is(prefs.selectedPane, "panePrivacy", "Privacy pane was selected");
@ -36,6 +37,28 @@ add_task(function*() {
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Test opening Preferences with subcategory on an existing Preferences tab. See bug 1358475.
add_task(function*() {
let prefs = yield openPreferencesViaOpenPreferencesAPI("general-search", {leaveOpen: true});
is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#general", "The subcategory should be removed from the URI");
ok(doc.querySelector("#startupGroup").hidden, "Startup should be hidden when only Search is requested");
ok(!doc.querySelector("#engineList").hidden, "The search engine list should be visible when Search is requested");
// The reasons that here just call the `openPreferences` API without the helping function are
// - already opened one about:preferences tab up there and
// - the goal is to test on the existing tab and
// - using `openPreferencesViaOpenPreferencesAPI` would introduce more handling of additional about:blank and unneccessary event
openPreferences("privacy-reports");
let selectedPane = gBrowser.contentWindow.history.state;
is(selectedPane, "panePrivacy", "Privacy pane should be selected");
is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
ok(doc.querySelector("#locationBarGroup").hidden, "Location Bar prefs should be hidden when only Reports are requested");
ok(!doc.querySelector("#header-privacy").hidden, "The header should be visible when a subcategory is requested");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
function openPreferencesViaHash(aPane) {
let deferred = Promise.defer();
gBrowser.selectedTab = gBrowser.addTab("about:preferences" + (aPane ? "#" + aPane : ""));

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

@ -4,9 +4,11 @@
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
const storageManagerDisabled = !SpecialPowers.getBoolPref("browser.storageManager.enabled");
const offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
function test() {
waitForExplicitFinish();
SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
open_preferences(runTest);
}
@ -21,12 +23,18 @@ function checkElements(expectedPane) {
element.id === "drmGroup") {
continue;
}
// The siteDataGroup in the Storage Management project is currently only pref-on on Nightly for testing purpose.
// During the test and the transition period, we have to check the pref to see if the siteDataGroup
// should be hidden always. This would be a bit bothersome, same as the offlineGroup as below.
// However, this checking is necessary to make sure we don't leak the siteDataGroup into beta/release build
if (element.id == "siteDataGroup" && storageManagerDisabled) {
is_element_hidden(element, "Disabled siteDataGroup should be hidden");
continue;
}
// The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
// so during the transition period, we have to check the pref to see if the offlineGroup
// should be hidden always. See the bug 1354530 for the details.
if (element.id == "offlineGroup" &&
!SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled")) {
if (element.id == "offlineGroup" && offlineGroupDisabled) {
is_element_hidden(element, "Disabled offlineGroup should be hidden");
continue;
}

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

@ -8,28 +8,21 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
function test() {
waitForExplicitFinish();
let prefs = [
"browser.cache.offline.enable",
"browser.cache.disk.enable",
"browser.cache.memory.enable",
];
for (let pref of prefs) {
Services.prefs.setBoolPref(pref, false);
}
// Adding one fake site so that the SiteDataManager would run.
// Otherwise, without any site then it would just return so we would end up in not testing SiteDataManager.
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.foo.com");
Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
registerCleanupFunction(function() {
for (let pref of prefs) {
Services.prefs.clearUserPref(pref);
}
Services.perms.removeFromPrincipal(principal, "persistent-storage");
});
open_preferences(runTest);
SpecialPowers.pushPrefEnv({set: [
["browser.cache.offline.enable", false],
["browser.cache.disk.enable", false],
["browser.cache.memory.enable", false],
["browser.storageManager.enabled", true],
["browser.preferences.offlineGroup.enabled", true]
]}).then(() => open_preferences(runTest));
}
function runTest(win) {
@ -37,7 +30,6 @@ function runTest(win) {
let tab = win.document;
let elements = tab.getElementById("mainPrefPane").children;
let offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
// Test if privacy pane is opened correctly
win.gotoPref("panePrivacy");
@ -45,13 +37,6 @@ function runTest(win) {
if (element.nodeName == "preferences") {
continue;
}
// The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
// so during the transition period, we have to check the pref to see if the offlineGroup
// should be hidden always. See the bug 1354530 for the details.
if (element.id == "offlineGroup" && offlineGroupDisabled) {
is_element_hidden(element, "Disabled offlineGroup should be hidden");
continue;
}
let attributeValue = element.getAttribute("data-category");
if (attributeValue == "panePrivacy") {
is_element_visible(element, "Privacy elements should be visible");

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

@ -242,7 +242,7 @@ var SessionSaverInternal = {
return Promise.resolve();
}
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
stopWatchStart("COLLECT_DATA_MS");
let state = SessionStore.getCurrentState(forceUpdateAllWindows);
PrivacyFilter.filterPrivateWindowsAndTabs(state);
@ -274,26 +274,44 @@ var SessionSaverInternal = {
}
}
// Clear all cookies and storage on clean shutdown according to user preferences
if (RunState.isClosing) {
// Clear cookies and storage on clean shutdown.
this._maybeClearCookiesAndStorage(state);
stopWatchFinish("COLLECT_DATA_MS");
return this._writeState(state);
},
/**
* Purges cookies and DOMSessionStorage data from the session on clean
* shutdown, only if requested by the user's preferences.
*/
_maybeClearCookiesAndStorage(state) {
// Only do this on shutdown.
if (!RunState.isClosing) {
return;
}
// Don't clear when restarting.
if (Services.prefs.getBoolPref("browser.sessionstore.resume_session_once")) {
return;
}
let expireCookies = Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
Services.cookies.QueryInterface(Ci.nsICookieService).ACCEPT_SESSION;
let sanitizeCookies = Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown") &&
Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies");
let restart = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
// Don't clear when restarting
if ((expireCookies || sanitizeCookies) && !restart) {
if (expireCookies || sanitizeCookies) {
// Remove cookies.
delete state.cookies;
// Remove DOMSessionStorage data.
for (let window of state.windows) {
delete window.cookies;
for (let tab of window.tabs) {
delete tab.storage;
}
}
}
}
stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
return this._writeState(state);
},
/**

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

@ -3138,9 +3138,7 @@ var SessionStoreInternal = {
};
// Collect and store session cookies.
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
state.cookies = SessionCookies.collect();
TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
if (Cu.isModuleLoaded("resource://devtools/client/scratchpad/scratchpad-manager.jsm")) {
// get open Scratchpad window states too
@ -3336,7 +3334,7 @@ var SessionStoreInternal = {
}
}
if (selectTab > 0) {
if (selectTab > 0 && selectTab <= tabs.length) {
// The state we're restoring wants to select a particular tab. This
// implies that we're overwriting tabs.
let currentIndex = tabbrowser.tabContainer.selectedIndex;
@ -3851,7 +3849,7 @@ var SessionStoreInternal = {
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
{loadArguments: aLoadArguments, isRemotenessUpdate,
reason: aReason});
reason: aReason, requestTime: Services.telemetry.msSystemNow()});
},
/**
@ -4406,8 +4404,8 @@ var SessionStoreInternal = {
if (tIndex + 1 < window.selected)
window.selected -= 1;
else if (tIndex + 1 == window.selected)
pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
// + 2 because the tab isn't actually in the array yet
pinnedWindowState.selected = pinnedWindowState.tabs.length + 1;
// + 1 because the tab isn't actually in the array yet
// Now add the pinned tab to our window
pinnedWindowState.tabs =

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

@ -144,6 +144,11 @@ var MessageListener = {
this.restoreHistory(data);
break;
case "SessionStore:restoreTabContent":
if (data.isRemotenessUpdate) {
let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS");
histogram.add("SessionStore:restoreTabContent",
Services.telemetry.msSystemNow() - data.requestTime);
}
this.restoreTabContent(data);
break;
case "SessionStore:resetRestore":

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

@ -104,8 +104,7 @@ add_task(function* test_remove_uninteresting_window() {
// history entries and make itself seem uninteresting.
yield ContentTask.spawn(browser, null, function*() {
// Epic hackery to make this browser seem suddenly boring.
Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
docShell.setCurrentURI(BrowserUtils.makeURI("about:blank"));
docShell.setCurrentURI(Services.io.newURI("about:blank"));
let {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
sessionHistory.PurgeHistory(sessionHistory.count);

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

@ -6,6 +6,7 @@ ac_add_options --disable-install-strip
ac_add_options --disable-jemalloc
ac_add_options --disable-crashreporter
ac_add_options --disable-elf-hack
ac_add_options --enable-debug
MOZ_CODE_COVERAGE=1
export CFLAGS="--coverage"

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

@ -1,16 +1,18 @@
/* 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/. */
/* globals Components, XPCOMUtils, Preferences, ActivityStream */
/* globals Components, XPCOMUtils, Preferences, Services, ActivityStream */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ActivityStream",
"resource://activity-stream/lib/ActivityStream.jsm");
const BROWSER_READY_NOTIFICATION = "browser-ui-startup-complete";
const ACTIVITY_STREAM_ENABLED_PREF = "browser.newtabpage.activity-stream.enabled";
const REASON_STARTUP_ON_PREF_CHANGE = "PREF_ON";
const REASON_SHUTDOWN_ON_PREF_CHANGE = "PREF_OFF";
@ -19,6 +21,7 @@ const ACTIVITY_STREAM_OPTIONS = {newTabURL: "about:newtab"};
let activityStream;
let startupData;
let startupReason;
/**
* init - Initializes an instance of ActivityStream. This could be called by
@ -64,27 +67,38 @@ function onPrefChanged(isEnabled) {
}
}
function observe(subject, topic, data) {
switch (topic) {
case BROWSER_READY_NOTIFICATION:
// Listen for changes to the pref that enables Activity Stream
Preferences.observe(ACTIVITY_STREAM_ENABLED_PREF, onPrefChanged);
// Only initialize if the pref is true
if (Preferences.get(ACTIVITY_STREAM_ENABLED_PREF)) {
init(startupReason);
Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
}
break;
}
}
// The functions below are required by bootstrap.js
this.install = function install(data, reason) {};
this.startup = function startup(data, reason) {
// Only start Activity Stream up when the browser UI is ready
Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
// Cache startup data which contains stuff like the version number, etc.
// so we can use it when we init
startupData = data;
// Listen for changes to the pref that enables Activity Stream
Preferences.observe(ACTIVITY_STREAM_ENABLED_PREF, onPrefChanged);
// Only initialize if the pref is true
if (Preferences.get(ACTIVITY_STREAM_ENABLED_PREF)) {
init(reason);
}
startupReason = reason;
};
this.shutdown = function shutdown(data, reason) {
// Uninitialize Activity Stream
startupData = null;
startupReason = null;
uninit(reason);
// Stop listening to the pref that enables Activity Stream

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

@ -3,15 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.MAIN_MESSAGE_TYPE = "ActivityStream:Main";
this.CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
this.actionTypes = [
const actionTypes = [
"INIT",
"UNINIT",
"NEW_TAB_INITIAL_STATE",
"NEW_TAB_LOAD",
"NEW_TAB_UNLOAD"
"NEW_TAB_UNLOAD",
"PERFORM_SEARCH",
"SCREENSHOT_UPDATED",
"SEARCH_STATE_UPDATED",
"TOP_SITES_UPDATED"
// The line below creates an object like this:
// {
// INIT: "INIT",
@ -86,6 +90,8 @@ function SendToContent(action, target) {
});
}
this.actionTypes = actionTypes;
this.actionCreators = {
SendToMain,
SendToContent,

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

@ -3,42 +3,63 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.INITIAL_STATE = {
const {actionTypes: at} = Components.utils.import("resource://activity-stream/common/Actions.jsm", {});
const INITIAL_STATE = {
TopSites: {
rows: [
{
"title": "Facebook",
"url": "https://www.facebook.com/"
init: false,
rows: []
},
{
"title": "YouTube",
"url": "https://www.youtube.com/"
Search: {
currentEngine: {
name: "",
icon: ""
},
{
"title": "Amazon",
"url": "http://www.amazon.com/"
},
{
"title": "Yahoo",
"url": "https://www.yahoo.com/"
},
{
"title": "eBay",
"url": "http://www.ebay.com"
},
{
"title": "Twitter",
"url": "https://twitter.com/"
}
]
engines: []
}
};
// TODO: Handle some real actions here, once we have a TopSites feed working
function TopSites(prevState = INITIAL_STATE.TopSites, action) {
let hasMatch;
let newRows;
switch (action.type) {
case at.TOP_SITES_UPDATED:
if (!action.data) {
return prevState;
}
return Object.assign({}, prevState, {init: true, rows: action.data});
case at.SCREENSHOT_UPDATED:
newRows = prevState.rows.map(row => {
if (row.url === action.data.url) {
hasMatch = true;
return Object.assign({}, row, {screenshot: action.data.screenshot});
}
return row;
});
return hasMatch ? Object.assign({}, prevState, {rows: newRows}) : prevState;
default:
return prevState;
}
}
this.reducers = {TopSites};
function Search(prevState = INITIAL_STATE.Search, action) {
switch (action.type) {
case at.SEARCH_STATE_UPDATED: {
if (!action.data) {
return prevState;
}
let {currentEngine, engines} = action.data;
return Object.assign({}, prevState, {
currentEngine,
engines
});
}
default:
return prevState;
}
}
this.INITIAL_STATE = INITIAL_STATE;
this.reducers = {TopSites, Search};
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE"];

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

@ -0,0 +1,570 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 10);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = React;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = ReactRedux;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* 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 MAIN_MESSAGE_TYPE = "ActivityStream:Main";
const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
const actionTypes = ["INIT", "UNINIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "PERFORM_SEARCH", "SCREENSHOT_UPDATED", "SEARCH_STATE_UPDATED", "TOP_SITES_UPDATED"
// The line below creates an object like this:
// {
// INIT: "INIT",
// UNINIT: "UNINIT"
// }
// It prevents accidentally adding a different key/value name.
].reduce((obj, type) => {
obj[type] = type;return obj;
}, {});
// Helper function for creating routed actions between content and main
// Not intended to be used by consumers
function _RouteMessage(action, options) {
const meta = action.meta ? Object.assign({}, action.meta) : {};
if (!options || !options.from || !options.to) {
throw new Error("Routed Messages must have options as the second parameter, and must at least include a .from and .to property.");
}
// For each of these fields, if they are passed as an option,
// add them to the action. If they are not defined, remove them.
["from", "to", "toTarget", "fromTarget", "skipOrigin"].forEach(o => {
if (typeof options[o] !== "undefined") {
meta[o] = options[o];
} else if (meta[o]) {
delete meta[o];
}
});
return Object.assign({}, action, { meta });
}
/**
* SendToMain - Creates a message that will be sent to the Main process.
*
* @param {object} action Any redux action (required)
* @param {object} options
* @param {string} options.fromTarget The id of the content port from which the action originated. (optional)
* @return {object} An action with added .meta properties
*/
function SendToMain(action) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return _RouteMessage(action, {
from: CONTENT_MESSAGE_TYPE,
to: MAIN_MESSAGE_TYPE,
fromTarget: options.fromTarget
});
}
/**
* BroadcastToContent - Creates a message that will be sent to ALL content processes.
*
* @param {object} action Any redux action (required)
* @return {object} An action with added .meta properties
*/
function BroadcastToContent(action) {
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE
});
}
/**
* SendToContent - Creates a message that will be sent to a particular Content process.
*
* @param {object} action Any redux action (required)
* @param {string} target The id of a content port
* @return {object} An action with added .meta properties
*/
function SendToContent(action, target) {
if (!target) {
throw new Error("You must provide a target ID as the second parameter of SendToContent. If you want to send to all content processes, use BroadcastToContent");
}
return _RouteMessage(action, {
from: MAIN_MESSAGE_TYPE,
to: CONTENT_MESSAGE_TYPE,
toTarget: target
});
}
var actionCreators = {
SendToMain,
SendToContent,
BroadcastToContent
};
// These are helpers to test for certain kinds of actions
var actionUtils = {
isSendToMain(action) {
if (!action.meta) {
return false;
}
return action.meta.to === MAIN_MESSAGE_TYPE && action.meta.from === CONTENT_MESSAGE_TYPE;
},
isBroadcastToContent(action) {
if (!action.meta) {
return false;
}
if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
return true;
}
return false;
},
isSendToContent(action) {
if (!action.meta) {
return false;
}
if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
return true;
}
return false;
},
_RouteMessage
};
module.exports = {
actionTypes,
actionCreators,
actionUtils,
MAIN_MESSAGE_TYPE,
CONTENT_MESSAGE_TYPE
};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const React = __webpack_require__(0);
const TopSites = __webpack_require__(8);
const Search = __webpack_require__(7);
const Base = () => React.createElement(
"div",
{ className: "outer-wrapper" },
React.createElement(
"main",
null,
React.createElement(Search, null),
React.createElement(TopSites, null)
)
);
module.exports = Base;
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals sendAsyncMessage, addMessageListener */
var _require = __webpack_require__(9);
const createStore = _require.createStore,
combineReducers = _require.combineReducers,
applyMiddleware = _require.applyMiddleware;
var _require2 = __webpack_require__(2);
const au = _require2.actionUtils;
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
/**
* A higher-order function which returns a reducer that, on MERGE_STORE action,
* will return the action.data object merged into the previous state.
*
* For all other actions, it merely calls mainReducer.
*
* Because we want this to merge the entire state object, it's written as a
* higher order function which takes the main reducer (itself often a call to
* combineReducers) as a parameter.
*
* @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
* @return {function} a reducer that, on MERGE_STORE_ACTION action,
* will return the action.data object merged
* into the previous state, and the result
* of calling mainReducer otherwise.
*/
function mergeStateReducer(mainReducer) {
return (prevState, action) => {
if (action.type === MERGE_STORE_ACTION) {
return Object.assign({}, prevState, action.data);
}
return mainReducer(prevState, action);
};
}
/**
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
const messageMiddleware = store => next => action => {
if (au.isSendToMain(action)) {
sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
}
next(action);
};
/**
* initStore - Create a store and listen for incoming actions
*
* @param {object} reducers An object containing Redux reducers
* @return {object} A redux store
*/
module.exports = function initStore(reducers) {
const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware));
addMessageListener(INCOMING_MESSAGE_NAME, msg => {
store.dispatch(msg.data);
});
return store;
};
module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION;
module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* 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/. */
var _require = __webpack_require__(2);
const at = _require.actionTypes;
const INITIAL_STATE = {
TopSites: {
init: false,
rows: []
},
Search: {
currentEngine: {
name: "",
icon: ""
},
engines: []
}
};
// TODO: Handle some real actions here, once we have a TopSites feed working
function TopSites() {
let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites;
let action = arguments[1];
let hasMatch;
let newRows;
switch (action.type) {
case at.TOP_SITES_UPDATED:
if (!action.data) {
return prevState;
}
return Object.assign({}, prevState, { init: true, rows: action.data });
case at.SCREENSHOT_UPDATED:
newRows = prevState.rows.map(row => {
if (row.url === action.data.url) {
hasMatch = true;
return Object.assign({}, row, { screenshot: action.data.screenshot });
}
return row;
});
return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState;
default:
return prevState;
}
}
function Search() {
let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Search;
let action = arguments[1];
switch (action.type) {
case at.SEARCH_STATE_UPDATED:
{
if (!action.data) {
return prevState;
}
var _action$data = action.data;
let currentEngine = _action$data.currentEngine,
engines = _action$data.engines;
return Object.assign({}, prevState, {
currentEngine,
engines
});
}
default:
return prevState;
}
}
var reducers = { TopSites, Search };
module.exports = {
reducers,
INITIAL_STATE
};
/***/ }),
/* 6 */
/***/ (function(module, exports) {
module.exports = ReactDOM;
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const React = __webpack_require__(0);
var _require = __webpack_require__(1);
const connect = _require.connect;
var _require2 = __webpack_require__(2);
const actionTypes = _require2.actionTypes,
actionCreators = _require2.actionCreators;
const Search = React.createClass({
displayName: "Search",
getInitialState() {
return { searchString: "" };
},
performSearch(options) {
let searchData = {
engineName: options.engineName,
searchString: options.searchString,
searchPurpose: "newtab",
healthReportKey: "newtab"
};
this.props.dispatch(actionCreators.SendToMain({ type: actionTypes.PERFORM_SEARCH, data: searchData }));
},
onClick(event) {
const currentEngine = this.props.Search.currentEngine;
event.preventDefault();
this.performSearch({ engineName: currentEngine.name, searchString: this.state.searchString });
},
onChange(event) {
this.setState({ searchString: event.target.value });
},
render() {
return React.createElement(
"form",
{ className: "search-wrapper" },
React.createElement("span", { className: "search-label" }),
React.createElement("input", { value: this.state.searchString, type: "search",
onChange: this.onChange,
maxLength: "256", title: "Submit search",
placeholder: "Search the Web" }),
React.createElement("button", { onClick: this.onClick })
);
}
});
module.exports = connect(state => ({ Search: state.Search }))(Search);
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const React = __webpack_require__(0);
var _require = __webpack_require__(1);
const connect = _require.connect;
function displayURL(url) {
return new URL(url).hostname.replace(/^www./, "");
}
const TopSites = props => React.createElement(
"section",
null,
React.createElement(
"h3",
{ className: "section-title" },
"Top Sites"
),
React.createElement(
"ul",
{ className: "top-sites-list" },
props.TopSites.rows.map(link => {
const title = displayURL(link.url);
const className = `screenshot${link.screenshot ? " active" : ""}`;
const style = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
return React.createElement(
"li",
{ key: link.url },
React.createElement(
"a",
{ href: link.url },
React.createElement(
"div",
{ className: "tile" },
React.createElement(
"span",
{ className: "letter-fallback", ariaHidden: true },
title[0]
),
React.createElement("div", { className: className, style: style })
),
React.createElement(
"div",
{ className: "title" },
title
)
)
);
})
)
);
module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSites);
/***/ }),
/* 9 */
/***/ (function(module, exports) {
module.exports = Redux;
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* globals addMessageListener, removeMessageListener */
const React = __webpack_require__(0);
const ReactDOM = __webpack_require__(6);
const Base = __webpack_require__(3);
var _require = __webpack_require__(1);
const Provider = _require.Provider;
const initStore = __webpack_require__(4);
var _require2 = __webpack_require__(5);
const reducers = _require2.reducers;
const store = initStore(reducers);
ReactDOM.render(React.createElement(
Provider,
{ store: store },
React.createElement(Base, null)
), document.getElementById("root"));
/***/ })
/******/ ]);

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

@ -0,0 +1,334 @@
html {
box-sizing: border-box; }
*,
*::before,
*::after {
box-sizing: inherit; }
body {
margin: 0; }
button,
input {
font-family: inherit;
font-size: inherit; }
[hidden] {
display: none !important; }
html,
body,
#root {
height: 100%; }
body {
background: #F6F6F8;
color: #383E49;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif;
font-size: 16px; }
h1,
h2 {
font-weight: normal; }
a {
color: #00AFF7;
text-decoration: none; }
a:hover {
color: #2bc1ff; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0; }
.inner-border {
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 3px;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100; }
@keyframes fadeIn {
from {
opacity: 0; }
to {
opacity: 1; } }
.show-on-init {
opacity: 0;
transition: opacity 0.2s ease-in; }
.show-on-init.on {
opacity: 1;
animation: fadeIn 0.2s; }
.actions {
border-top: solid 1px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: row;
margin: 0;
padding: 15px 25px;
justify-content: flex-start; }
.actions button {
background: #FBFBFB;
border: solid 1px #BFBFBF;
border-radius: 5px;
color: #858585;
cursor: pointer;
padding: 10px 30px; }
.actions button:hover {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 150ms; }
.actions button.done {
background: #0695F9;
border: solid 1px #1677CF;
color: #FFF;
margin-inline-start: auto; }
.outer-wrapper {
display: flex;
flex-grow: 1;
padding: 62px 32px 32px;
height: 100%; }
main {
margin: auto; }
@media (min-width: 672px) {
main {
width: 608px; } }
@media (min-width: 800px) {
main {
width: 736px; } }
main section {
margin-bottom: 41px; }
.section-title {
color: #6E707E;
font-size: 13px;
font-weight: bold;
text-transform: uppercase;
margin: 0 0 18px; }
.top-sites-list {
list-style: none;
margin: 0;
padding: 0;
margin-inline-end: -32px; }
@media (min-width: 672px) {
.top-sites-list {
width: 640px; } }
@media (min-width: 800px) {
.top-sites-list {
width: 768px; } }
.top-sites-list li {
display: inline-block;
margin: 0 0 18px;
margin-inline-end: 32px; }
.top-sites-list a {
display: block;
color: inherit; }
.top-sites-list .tile {
position: relative;
height: 96px;
width: 96px;
border-radius: 6px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
color: #A0A0A0;
font-weight: 200;
font-size: 32px;
text-transform: uppercase;
display: flex;
align-items: center;
justify-content: center; }
.top-sites-list .tile:hover {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 150ms; }
.top-sites-list .screenshot {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #FFF;
border-radius: 6px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
background-size: 250%;
background-position: top left;
transition: opacity 1s;
opacity: 0; }
.top-sites-list .screenshot.active {
opacity: 1; }
.top-sites-list .title {
height: 30px;
line-height: 30px;
text-align: center;
white-space: nowrap;
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
width: 96px; }
.search-wrapper {
cursor: default;
display: flex;
position: relative;
margin: 0 0 48px;
width: 100%;
height: 36px; }
.search-wrapper .search-container {
z-index: 1001;
background: #FFF;
position: absolute;
left: 0;
right: 0;
top: 100%;
margin-top: -2px;
border: 1px solid #BFBFBF;
font-size: 12px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
overflow: hidden; }
.search-wrapper .search-container .search-title {
color: #666;
padding: 5px 10px;
background-color: #F7F7F7;
display: flex;
align-items: center;
word-break: break-all; }
.search-wrapper .search-container .search-title p {
margin: 0; }
.search-wrapper .search-container .search-title #current-engine-icon {
margin-inline-end: 8px; }
.search-wrapper .search-container section {
border-bottom: 1px solid #EAEAEA;
margin-bottom: 0; }
.search-wrapper .search-container .search-suggestions ul {
padding: 0;
margin: 0;
list-style: none; }
.search-wrapper .search-container .search-suggestions ul li a {
cursor: default;
color: #000;
display: block;
padding: 4px 36px; }
.search-wrapper .search-container .search-suggestions ul li a:hover, .search-wrapper .search-container .search-suggestions ul li a.active {
background: #0996F8;
color: #FFF; }
.search-wrapper .search-container .history-search-suggestions {
border-bottom: 0; }
.search-wrapper .search-container .history-search-suggestions ul {
padding: 0;
margin: 0;
list-style: none; }
.search-wrapper .search-container .history-search-suggestions ul li a {
cursor: default;
color: #000;
display: block;
padding: 4px 10px; }
.search-wrapper .search-container .history-search-suggestions ul li a:hover, .search-wrapper .search-container .history-search-suggestions ul li a.active {
background: #0996F8;
color: #FFF; }
.search-wrapper .search-container .history-search-suggestions ul li a:hover > #historyIcon,
.search-wrapper .search-container .history-search-suggestions ul li a.active > #historyIcon {
background-image: url("assets/glyph-search-history.svg#search-history-active"); }
.search-wrapper .search-container .history-search-suggestions #historyIcon {
width: 16px;
height: 16px;
display: inline-block;
margin-inline-end: 10px;
margin-bottom: -3px;
background-image: url("assets/glyph-search-history.svg#search-history"); }
.search-wrapper .search-container .search-partners ul {
padding: 0;
margin: 0;
list-style: none; }
.search-wrapper .search-container .search-partners ul li {
display: inline-block;
padding: 5px 0; }
.search-wrapper .search-container .search-partners ul li a {
display: block;
padding: 3px 16px;
border-right: 1px solid #BFBFBF; }
.search-wrapper .search-container .search-partners ul li:hover, .search-wrapper .search-container .search-partners ul li.active {
background: #0996F8;
color: #FFF; }
.search-wrapper .search-container .search-partners ul li:hover a, .search-wrapper .search-container .search-partners ul li.active a {
border-color: transparent; }
.search-wrapper .search-container .search-settings button {
color: #666;
margin: 0;
padding: 0;
height: 32px;
text-align: center;
width: 100%;
border-style: solid none none;
border-radius: 0;
background: #F7F7F7;
border-top: 0; }
.search-wrapper .search-container .search-settings button:hover, .search-wrapper .search-container .search-settings button.active {
background: #EBEBEB;
box-shadow: none; }
.search-wrapper input {
border: 0;
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
flex-grow: 1;
margin: 0;
outline: none;
padding: 0 12px 0 35px;
height: 100%;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
padding-inline-start: 35px; }
.search-wrapper input:focus {
border-color: #0996F8;
box-shadow: 0 0 0 2px #0996F8;
transition: box-shadow 150ms;
z-index: 1; }
.search-wrapper input:focus + button {
z-index: 1;
transition: box-shadow 150ms;
box-shadow: 0 0 0 2px #0996F8;
background-color: #0996F8;
background-image: url("assets/glyph-forward-16-white.svg");
color: #FFF; }
.search-wrapper input:dir(rtl) {
border-radius: 0 4px 4px 0; }
.search-wrapper .search-label {
background: url("assets/glyph-search-16.svg") no-repeat center center/20px;
position: absolute;
top: 0;
offset-inline-start: 0;
height: 100%;
width: 35px;
display: flex;
align-items: center;
justify-content: center;
z-index: 2; }
.search-wrapper button {
border-radius: 0 3px 3px 0;
margin-inline-start: -1px;
border: 0;
width: 36px;
padding: 0;
transition: box-shadow 150ms;
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
background: #FFF url("assets/glyph-forward-16.svg") no-repeat center center;
background-size: 16px 16px; }
.search-wrapper button:hover {
z-index: 1;
transition: box-shadow 150ms;
box-shadow: 0 1px 0 0 rgba(0, 0, 1, 0.5);
background-color: #0996F8;
background-image: url("assets/glyph-forward-16-white.svg");
color: #FFF; }
.search-wrapper button:dir(rtl) {
transform: scaleX(-1); }

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

@ -3,29 +3,14 @@
<head>
<meta charset="utf-8">
<title>New Tab</title>
<link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
</head>
<body>
<div id="root">
<h1>New Tab</h1>
<ul id="top-sites"></ul>
</div>
<script>
const topSitesEl = document.getElementById("top-sites");
window.addMessageListener("ActivityStream:MainToContent", msg => {
if (msg.data.type === "NEW_TAB_INITIAL_STATE") {
const fragment = document.createDocumentFragment()
for (const row of msg.data.data.TopSites.rows) {
const li = document.createElement("li");
const a = document.createElement("a");
a.href = row.url;
a.textContent = row.title;
li.appendChild(a);
fragment.appendChild(li);
}
topSitesEl.appendChild(fragment);
}
});
</script>
<body class="activity-stream">
<div id="root"></div>
<script src="resource://activity-stream/vendor/react.js"></script>
<script src="resource://activity-stream/vendor/react-dom.js"></script>
<script src="resource://activity-stream/vendor/redux.js"></script>
<script src="resource://activity-stream/vendor/react-redux.js"></script>
<script src="resource://activity-stream/data/content/activity-stream.bundle.js"></script>
</body>
</html>

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

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Forward - 16</title>
<g>
<polyline points="9 2 15 8 9 14" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
</g>
</svg>

После

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

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

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Forward - 16</title>
<g>
<polyline points="9 2 15 8 9 14" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
</g>
</svg>

После

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

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

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<defs>
<style>
.cls-1 {
fill: #a0a0a0;
fill-rule: evenodd;
}
</style>
</defs>
<g id="glyph-search-16">
<path id="Icon_-_Search_-_16" data-name="Icon - Search - 16" class="cls-1" d="M226.989,348.571l-2.2,2.2-9.533-9.534a11.436,11.436,0,1,1,2.2-2.2ZM208.37,323.745a8.407,8.407,0,1,0,8.406,8.406A8.406,8.406,0,0,0,208.37,323.745Z" transform="translate(-196 -320)"/>
</g>
</svg>

После

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

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

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
<style>
use:not(:target) {
display: none;
}
use {
fill: graytext;
}
use[id$="-active"] {
fill: HighlightText;
}
</style>
<defs>
<path id="search-history-glyph" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7 C15,4.1,11.9,1,8,1z M8,13.3c-2.9,0-5.3-2.4-5.3-5.3S5.1,2.7,8,2.7c2.9,0,5.3,2.4,5.3,5.3S10.9,13.3,8,13.3z M10.5,7H9V5 c0-0.6-0.4-1-1-1S7,4.4,7,5v3c0,0.6,0.4,1,1,1h2.5c0.6,0,1-0.4,1-1C11.5,7.4,11.1,7,10.5,7z"/>
</defs>
<use id="search-history" xlink:href="#search-history-glyph"/>
<use id="search-history-active" xlink:href="#search-history-glyph"/>
</svg>

После

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

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

@ -7,4 +7,8 @@
content/lib/ (./lib/*)
content/common/ (./common/*)
content/vendor/Redux.jsm (./vendor/Redux.jsm)
content/vendor/react.js (./vendor/react.js)
content/vendor/react-dom.js (./vendor/react-dom.js)
content/vendor/redux.js (./vendor/redux.js)
content/vendor/react-redux.js (./vendor/react-redux.js)
content/data/ (./data/*)

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

@ -1,13 +1,35 @@
/* 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/. */
/* globals XPCOMUtils, NewTabInit, TopSitesFeed, SearchFeed */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {Store} = Cu.import("resource://activity-stream/lib/Store.jsm", {});
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
// Feeds
const {NewTabInit} = Cu.import("resource://activity-stream/lib/NewTabInit.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "NewTabInit",
"resource://activity-stream/lib/NewTabInit.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TopSitesFeed",
"resource://activity-stream/lib/TopSitesFeed.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SearchFeed",
"resource://activity-stream/lib/SearchFeed.jsm");
const feeds = {
// When you add a feed here:
// 1. The key in this object should directly refer to a pref, not including the
// prefix (so "feeds.newtabinit" refers to the
// "browser.newtabpage.activity-stream.feeds.newtabinit" pref)
// 2. The value should be a function that returns a feed.
// 3. You should use XPCOMUtils.defineLazyModuleGetter to import the Feed,
// so it isn't loaded until the feed is enabled.
"feeds.newtabinit": () => new NewTabInit(),
"feeds.topsites": () => new TopSitesFeed(),
"feeds.search": () => new SearchFeed()
};
this.ActivityStream = class ActivityStream {
@ -23,14 +45,15 @@ this.ActivityStream = class ActivityStream {
this.initialized = false;
this.options = options;
this.store = new Store();
this.feeds = feeds;
}
init() {
this.initialized = true;
this.store.init([
new NewTabInit()
]);
this.store.init(this.feeds);
this.store.dispatch({type: at.INIT});
}
uninit() {
this.store.dispatch({type: at.UNINIT});
this.store.uninit();
this.initialized = false;
}

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

@ -33,6 +33,7 @@ const DEFAULT_OPTIONS = {
};
this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
/**
* ActivityStreamMessageChannel - This module connects a Redux store to a RemotePageManager in Firefox.
* Call .createChannel to start the connection, and .destroyChannel to destroy it.
@ -183,13 +184,17 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
* @param {obj} msg.target A message target
*/
onMessage(msg) {
const action = msg.data;
const {portID} = msg.target;
if (!action || !action.type) {
if (!msg.data || !msg.data.type) {
Cu.reportError(new Error(`Received an improperly formatted message from ${portID}`));
return;
}
this.onActionFromContent(action, msg.target.portID);
let action = {};
Object.assign(action, msg.data);
// target is used to access a browser reference that came from the content
// and should only be used in feeds (not reducers)
action._target = msg.target;
this.onActionFromContent(action, portID);
}
}

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

@ -0,0 +1,65 @@
/* 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/. */
/* globals ContentSearch, XPCOMUtils, Services */
"use strict";
const {utils: Cu} = Components;
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
"resource:///modules/ContentSearch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
this.SearchFeed = class SearchFeed {
addObservers() {
Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC);
}
removeObservers() {
Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
}
observe(subject, topic, data) {
switch (topic) {
case SEARCH_ENGINE_TOPIC:
if (data !== "engine-default") {
this.getState();
}
break;
}
}
async getState() {
const state = await ContentSearch.currentStateObj(true);
const engines = state.engines.map(engine => ({
name: engine.name,
icon: engine.iconBuffer
}));
const currentEngine = {
name: state.currentEngine.name,
icon: state.currentEngine.iconBuffer
};
const action = {type: at.SEARCH_STATE_UPDATED, data: {engines, currentEngine}};
this.store.dispatch(ac.BroadcastToContent(action));
}
performSearch(browser, data) {
ContentSearch.performSearch({target: browser}, data);
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.addObservers();
this.getState();
break;
case at.PERFORM_SEARCH:
this.performSearch(action._target.browser, action.data);
break;
case at.UNINIT:
this.removeObservers();
break;
}
}
};
this.EXPORTED_SYMBOLS = ["SearchFeed"];

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

@ -1,15 +1,18 @@
/* 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/. */
/* global Preferences */
"use strict";
const {utils: Cu} = Components;
const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {});
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
const {ActivityStreamMessageChannel} = Cu.import("resource://activity-stream/lib/ActivityStreamMessageChannel.jsm", {});
const PREF_PREFIX = "browser.newtabpage.activity-stream.";
Cu.import("resource://gre/modules/Preferences.jsm");
/**
* Store - This has a similar structure to a redux store, but includes some extra
* functionality to allow for routing of actions between the Main processes
@ -32,7 +35,9 @@ this.Store = class Store {
return this._store[method](...args);
}.bind(this);
});
this.feeds = new Set();
this.feeds = new Map();
this._feedFactories = null;
this._prefHandlers = new Map();
this._messageChannel = new ActivityStreamMessageChannel({dispatch: this.dispatch});
this._store = redux.createStore(
redux.combineReducers(reducers),
@ -53,33 +58,93 @@ this.Store = class Store {
}
/**
* init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
* After initialization has finished, an INIT action is dispatched.
* initFeed - Initializes a feed by calling its constructor function
*
* @param {array} feeds An array of objects with an optional .onAction method
* @param {string} feedName The name of a feed, as defined in the object
* passed to Store.init
*/
init(feeds) {
if (feeds) {
feeds.forEach(subscriber => {
subscriber.store = this;
this.feeds.add(subscriber);
});
}
this._messageChannel.createChannel();
this.dispatch({type: at.INIT});
initFeed(feedName) {
const feed = this._feedFactories[feedName]();
feed.store = this;
this.feeds.set(feedName, feed);
}
/**
* uninit - Clears all feeds, dispatches an UNINIT action, and
* destroys the message manager channel.
* uninitFeed - Removes a feed and calls its uninit function if defined
*
* @param {string} feedName The name of a feed, as defined in the object
* passed to Store.init
*/
uninitFeed(feedName) {
const feed = this.feeds.get(feedName);
if (!feed) {
return;
}
if (feed.uninit) {
feed.uninit();
}
this.feeds.delete(feedName);
}
/**
* maybeStartFeedAndListenForPrefChanges - Listen for pref changes that turn a
* feed off/on, and as long as that pref was not explicitly set to
* false, initialize the feed immediately.
*
* @param {string} name The name of a feed, as defined in the object passed
* to Store.init
*/
maybeStartFeedAndListenForPrefChanges(name) {
const prefName = PREF_PREFIX + name;
// If the pref was never set, set it to true by default.
if (!Preferences.has(prefName)) {
Preferences.set(prefName, true);
}
// Create a listener that turns the feed off/on based on changes
// to the pref, and cache it so we can unlisten on shut-down.
const onPrefChanged = isEnabled => (isEnabled ? this.initFeed(name) : this.uninitFeed(name));
this._prefHandlers.set(prefName, onPrefChanged);
Preferences.observe(prefName, onPrefChanged);
// TODO: This should propbably be done in a generic pref manager for Activity Stream.
// If the pref is true, start the feed immediately.
if (Preferences.get(prefName)) {
this.initFeed(name);
}
}
/**
* init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
*
* @param {array} feeds An array of objects with an optional .onAction method
*/
init(feedConstructors) {
if (feedConstructors) {
this._feedFactories = feedConstructors;
for (const name of Object.keys(feedConstructors)) {
this.maybeStartFeedAndListenForPrefChanges(name);
}
}
this._messageChannel.createChannel();
}
/**
* uninit - Uninitalizes each feed, clears them, and destroys the message
* manager channel.
*
* @return {type} description
*/
uninit() {
this.feeds.forEach(feed => this.uninitFeed(feed));
this._prefHandlers.forEach((handler, pref) => Preferences.ignore(pref, handler));
this._prefHandlers.clear();
this._feedFactories = null;
this.feeds.clear();
this.dispatch({type: at.UNINIT});
this._messageChannel.destroyChannel();
}
};
this.EXPORTED_SYMBOLS = ["Store"];
this.PREF_PREFIX = PREF_PREFIX;
this.EXPORTED_SYMBOLS = ["Store", "PREF_PREFIX"];

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

@ -0,0 +1,83 @@
/* 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/. */
/* globals PlacesProvider, PreviewProvider */
"use strict";
const {utils: Cu} = Components;
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
Cu.import("resource:///modules/PlacesProvider.jsm");
Cu.import("resource:///modules/PreviewProvider.jsm");
const TOP_SITES_SHOWMORE_LENGTH = 12;
const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
const DEFAULT_TOP_SITES = [
{"url": "https://www.facebook.com/"},
{"url": "https://www.youtube.com/"},
{"url": "http://www.amazon.com/"},
{"url": "https://www.yahoo.com/"},
{"url": "http://www.ebay.com"},
{"url": "https://twitter.com/"}
].map(row => Object.assign(row, {isDefault: true}));
this.TopSitesFeed = class TopSitesFeed {
constructor() {
this.lastUpdated = 0;
}
async getScreenshot(url) {
let screenshot = await PreviewProvider.getThumbnail(url);
const action = {type: at.SCREENSHOT_UPDATED, data: {url, screenshot}};
this.store.dispatch(ac.BroadcastToContent(action));
}
async getLinksWithDefaults(action) {
let links = await PlacesProvider.links.getLinks();
if (!links) {
links = [];
} else {
links = links.filter(link => link && link.type !== "affiliate").slice(0, 12);
}
if (links.length < TOP_SITES_SHOWMORE_LENGTH) {
links = [...links, ...DEFAULT_TOP_SITES].slice(0, TOP_SITES_SHOWMORE_LENGTH);
}
return links;
}
async refresh(action) {
const links = await this.getLinksWithDefaults();
const newAction = {type: at.TOP_SITES_UPDATED, data: links};
// Send an update to content so the preloaded tab can get the updated content
this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
this.lastUpdated = Date.now();
// Now, get a screenshot for every item
for (let link of links) {
this.getScreenshot(link.url);
}
}
onAction(action) {
let realRows;
switch (action.type) {
case at.NEW_TAB_LOAD:
// Only check against real rows returned from history, not default ones.
realRows = this.store.getState().TopSites.rows.filter(row => !row.isDefault);
// When a new tab is opened, if we don't have enough top sites yet, refresh the data.
if (realRows.length < TOP_SITES_SHOWMORE_LENGTH) {
this.refresh(action);
} else if (Date.now() - this.lastUpdated >= UPDATE_TIME) {
// When a new tab is opened, if the last time we refreshed the data
// is greater than 15 minutes, refresh the data.
this.refresh(action);
}
break;
}
}
};
this.UPDATE_TIME = UPDATE_TIME;
this.TOP_SITES_SHOWMORE_LENGTH = TOP_SITES_SHOWMORE_LENGTH;
this.DEFAULT_TOP_SITES = DEFAULT_TOP_SITES;
this.EXPORTED_SYMBOLS = ["TopSitesFeed", "UPDATE_TIME", "DEFAULT_TOP_SITES", "TOP_SITES_SHOWMORE_LENGTH"];

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

@ -0,0 +1,11 @@
module.exports = {
"env": {
"node": true,
"es6": true,
"mocha": true
},
"globals": {
"assert": true,
"sinon": true
}
};

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

@ -0,0 +1,42 @@
module.exports = {
"globals": {
"add_task": false,
"Assert": false,
"BrowserOpenTab": false,
"BrowserTestUtils": false,
"content": false,
"ContentTask": false,
"ContentTaskUtils": false,
"Components": false,
"EventUtils": false,
"executeSoon": false,
"expectUncaughtException": false,
"export_assertions": false,
"extractJarToTmp": false,
"finish": false,
"getJar": false,
"getRootDirectory": false,
"getTestFilePath": false,
"gBrowser": false,
"gTestPath": false,
"info": false,
"is": false,
"isnot": false,
"ok": false,
"OpenBrowserWindow": false,
"Preferences": false,
"registerCleanupFunction": false,
"requestLongerTimeout": false,
"Services": false,
"SimpleTest": false,
"SpecialPowers": false,
"TestUtils": false,
"thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
"todo": false,
"todo_is": false,
"todo_isnot": false,
"waitForClipboard": false,
"waitForExplicitFinish": false,
"waitForFocus": false
}
};

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

@ -0,0 +1,13 @@
[DEFAULT]
# XXX This defaults to forcing activity-stream tests to be skipped in m-c,
# since, as of this writing, mozilla-central itself is still turned off.
# The tests can be run locally using 'npm run mochitest' which does various
# overrides.
skip-if=!activity_stream
[browser_dummy_test.js]
skip-if=true
# XXX The above test is required because having only one test causes
# The default skip-if to silently fail. As soon as we add another test here,
# we should get rid of it, and the following line.
[browser_as_load_location.js]

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

@ -0,0 +1,34 @@
"use strict";
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
/**
* Tests that opening a new tab opens a page with the expected activity stream
* content.
*
* XXX /browser/components/newtab/tests/browser/browser_newtab_overrides in
* mozilla-central is where this test was adapted from. Once we get decide on
* and implement how we're going to set the URL in mozilla-central, we may well
* want to (separately from this test), clone/adapt that entire file for our
* new setup.
*/
add_task(async function checkActivityStreamLoads() {
const asURL = "resource://activity-stream/data/content/activity-stream.html";
// simulate a newtab open as a user would
BrowserOpenTab();
// wait until the browser loads
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.browserLoaded(browser);
// check what the content task thinks has been loaded.
await ContentTask.spawn(browser, {url: asURL}, args => {
Assert.ok(content.document.querySelector("body.activity-stream"),
'Got <body class="activity-stream" Element');
});
// avoid leakage
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -0,0 +1,34 @@
"use strict";
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
/**
* Tests that opening a new tab opens a page with the expected activity stream
* content.
*
* XXX /browser/components/newtab/tests/browser/browser_newtab_overrides in
* mozilla-central is where this test was adapted from. Once we get decide on
* and implement how we're going to set the URL in mozilla-central, we may well
* want to (separately from this test), clone/adapt that entire file for our
* new setup.
*/
add_task(async function checkActivityStreamLoads() {
const asURL = "resource://activity-stream/data/content/activity-stream.html";
// simulate a newtab open as a user would
BrowserOpenTab();
// wait until the browser loads
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.browserLoaded(browser);
// check what the content task thinks has been loaded.
await ContentTask.spawn(browser, {url: asURL}, args => {
Assert.ok(content.document.querySelector("body.activity-stream"),
'Got <body class="activity-stream" Element');
});
// avoid leakage
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -0,0 +1,3 @@
{
"activity_stream": true
}

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

@ -0,0 +1,93 @@
const {
actionCreators: ac,
actionUtils: au,
MAIN_MESSAGE_TYPE,
CONTENT_MESSAGE_TYPE
} = require("common/Actions.jsm");
describe("ActionCreators", () => {
describe("_RouteMessage", () => {
it("should throw if options are not passed as the second param", () => {
assert.throws(() => {
au._RouteMessage({type: "FOO"});
});
});
it("should set all defined options on the .meta property of the new action", () => {
assert.deepEqual(
au._RouteMessage({type: "FOO", meta: {hello: "world"}}, {from: "foo", to: "bar"}),
{type: "FOO", meta: {hello: "world", from: "foo", to: "bar"}}
);
});
it("should remove any undefined options related to message routing", () => {
const action = au._RouteMessage({type: "FOO", meta: {fromTarget: "bar"}}, {from: "foo", to: "bar"});
assert.isUndefined(action.meta.fromTarget);
});
});
describe("SendToMain", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const newAction = ac.SendToMain(action);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
meta: {from: CONTENT_MESSAGE_TYPE, to: MAIN_MESSAGE_TYPE}
});
});
describe("isSendToMain", () => {
it("should return true if action is SendToMain", () => {
const newAction = ac.SendToMain({type: "FOO"});
assert.isTrue(au.isSendToMain(newAction));
});
it("should return false if action is not SendToMain", () => {
assert.isFalse(au.isSendToMain({type: "FOO"}));
});
});
});
describe("SendToContent", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const targetId = "abc123";
const newAction = ac.SendToContent(action, targetId);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
meta: {from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE, toTarget: targetId}
});
});
it("should throw if no targetId is provided", () => {
assert.throws(() => {
ac.SendToContent({type: "FOO"});
});
});
describe("isSendToContent", () => {
it("should return true if action is SendToContent", () => {
const newAction = ac.SendToContent({type: "FOO"}, "foo123");
assert.isTrue(au.isSendToContent(newAction));
});
it("should return false if action is not SendToMain", () => {
assert.isFalse(au.isSendToContent({type: "FOO"}));
assert.isFalse(au.isSendToContent(ac.BroadcastToContent({type: "FOO"})));
});
});
});
describe("BroadcastToContent", () => {
it("should create the right action", () => {
const action = {type: "FOO", data: "BAR"};
const newAction = ac.BroadcastToContent(action);
assert.deepEqual(newAction, {
type: "FOO",
data: "BAR",
meta: {from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE}
});
});
describe("isBroadcastToContent", () => {
it("should return true if action is BroadcastToContent", () => {
assert.isTrue(au.isBroadcastToContent(ac.BroadcastToContent({type: "FOO"})));
});
it("should return false if action is not BroadcastToContent", () => {
assert.isFalse(au.isBroadcastToContent({type: "FOO"}));
assert.isFalse(au.isBroadcastToContent(ac.SendToContent({type: "FOO"}, "foo123")));
});
});
});
});

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

@ -0,0 +1,51 @@
const {reducers, INITIAL_STATE} = require("common/Reducers.jsm");
const {TopSites, Search} = reducers;
const {actionTypes: at} = require("common/Actions.jsm");
describe("Reducers", () => {
describe("TopSites", () => {
it("should return the initial state", () => {
const nextState = TopSites(undefined, {type: "FOO"});
assert.equal(nextState, INITIAL_STATE.TopSites);
});
it("should add top sites on TOP_SITES_UPDATED", () => {
const newRows = [{url: "foo.com"}, {url: "bar.com"}];
const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED, data: newRows});
assert.equal(nextState.rows, newRows);
});
it("should not update state for empty action.data on TOP_SITES_UPDATED", () => {
const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED});
assert.equal(nextState, INITIAL_STATE.TopSites);
});
it("should add screenshots for SCREENSHOT_UPDATED", () => {
const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
const action = {type: at.SCREENSHOT_UPDATED, data: {url: "bar.com", screenshot: "data:123"}};
const nextState = TopSites(oldState, action);
assert.deepEqual(nextState.rows, [{url: "foo.com"}, {url: "bar.com", screenshot: "data:123"}]);
});
it("should not modify rows if nothing matches the url for SCREENSHOT_UPDATED", () => {
const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
const action = {type: at.SCREENSHOT_UPDATED, data: {url: "baz.com", screenshot: "data:123"}};
const nextState = TopSites(oldState, action);
assert.deepEqual(nextState, oldState);
});
});
describe("Search", () => {
it("should return the initial state", () => {
const nextState = Search(undefined, {type: "FOO"});
assert.equal(nextState, INITIAL_STATE.Search);
});
it("should not update state for empty action.data on Search", () => {
const nextState = Search(undefined, {type: at.SEARCH_STATE_UPDATED});
assert.equal(nextState, INITIAL_STATE.Search);
});
it("should update the current engine and the engines on SEARCH_STATE_UPDATED", () => {
const newEngine = {name: "Google", iconBuffer: "icon.ico"};
const nextState = Search(undefined, {type: at.SEARCH_STATE_UPDATED, data: {currentEngine: newEngine, engines: [newEngine]}});
assert.equal(nextState.currentEngine.name, newEngine.name);
assert.equal(nextState.currentEngine.icon, newEngine.icon);
assert.equal(nextState.engines[0].name, newEngine.name);
assert.equal(nextState.engines[0].icon, newEngine.icon);
});
});
});

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

@ -0,0 +1,70 @@
const injector = require("inject!lib/ActivityStream.jsm");
describe("ActivityStream", () => {
let sandbox;
let as;
let ActivityStream;
function NewTabInit() {}
function TopSitesFeed() {}
function SearchFeed() {}
before(() => {
sandbox = sinon.sandbox.create();
({ActivityStream} = injector({
"lib/NewTabInit.jsm": {NewTabInit},
"lib/TopSitesFeed.jsm": {TopSitesFeed},
"lib/SearchFeed.jsm": {SearchFeed}
}));
});
afterEach(() => sandbox.restore());
beforeEach(() => {
as = new ActivityStream();
sandbox.stub(as.store, "init");
sandbox.stub(as.store, "uninit");
});
it("should exist", () => {
assert.ok(ActivityStream);
});
it("should initialize with .initialized=false", () => {
assert.isFalse(as.initialized, ".initialized");
});
describe("#init", () => {
beforeEach(() => {
as.init();
});
it("should set .initialized to true", () => {
assert.isTrue(as.initialized, ".initialized");
});
it("should call .store.init", () => {
assert.calledOnce(as.store.init);
});
});
describe("#uninit", () => {
beforeEach(() => {
as.init();
as.uninit();
});
it("should set .initialized to false", () => {
assert.isFalse(as.initialized, ".initialized");
});
it("should call .store.uninit", () => {
assert.calledOnce(as.store.uninit);
});
});
describe("feeds", () => {
it("should create a NewTabInit feed", () => {
const feed = as.feeds["feeds.newtabinit"]();
assert.instanceOf(feed, NewTabInit);
});
it("should create a TopSites feed", () => {
const feed = as.feeds["feeds.topsites"]();
assert.instanceOf(feed, TopSitesFeed);
});
it("should create a Search feed", () => {
const feed = as.feeds["feeds.search"]();
assert.instanceOf(feed, SearchFeed);
});
});
});

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

@ -0,0 +1,235 @@
const {ActivityStreamMessageChannel, DEFAULT_OPTIONS} = require("lib/ActivityStreamMessageChannel.jsm");
const {addNumberReducer, GlobalOverrider} = require("test/unit/utils");
const {createStore, applyMiddleware} = require("redux");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
const OPTIONS = ["pageURL, outgoingMessageName", "incomingMessageName", "dispatch"];
describe("ActivityStreamMessageChannel", () => {
let globals;
let dispatch;
let mm;
before(() => {
function RP(url) {
this.url = url;
this.messagePorts = [];
this.addMessageListener = globals.sandbox.spy();
this.sendAsyncMessage = globals.sandbox.spy();
this.destroy = globals.sandbox.spy();
}
globals = new GlobalOverrider();
globals.set("AboutNewTab", {
override: globals.sandbox.spy(),
reset: globals.sandbox.spy()
});
globals.set("RemotePages", RP);
dispatch = globals.sandbox.spy();
});
beforeEach(() => {
mm = new ActivityStreamMessageChannel({dispatch});
});
afterEach(() => globals.reset());
after(() => globals.restore());
it("should exist", () => {
assert.ok(ActivityStreamMessageChannel);
});
it("should apply default options", () => {
mm = new ActivityStreamMessageChannel();
OPTIONS.forEach(o => assert.equal(mm[o], DEFAULT_OPTIONS[o], o));
});
it("should add options", () => {
const options = {dispatch: () => {}, pageURL: "FOO.html", outgoingMessageName: "OUT", incomingMessageName: "IN"};
mm = new ActivityStreamMessageChannel(options);
OPTIONS.forEach(o => assert.equal(mm[o], options[o], o));
});
it("should throw an error if no dispatcher was provided", () => {
mm = new ActivityStreamMessageChannel();
assert.throws(() => mm.dispatch({type: "FOO"}));
});
describe("Creating/destroying the channel", () => {
describe("#createChannel", () => {
it("should create .channel with the correct URL", () => {
mm.createChannel();
assert.ok(mm.channel);
assert.equal(mm.channel.url, mm.pageURL);
});
it("should add 3 message listeners", () => {
mm.createChannel();
assert.callCount(mm.channel.addMessageListener, 3);
});
it("should add the custom message listener to the channel", () => {
mm.createChannel();
assert.calledWith(mm.channel.addMessageListener, mm.incomingMessageName, mm.onMessage);
});
it("should override AboutNewTab", () => {
mm.createChannel();
assert.calledOnce(global.AboutNewTab.override);
});
it("should not override AboutNewTab if the pageURL is not about:newtab", () => {
mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
mm.createChannel();
assert.notCalled(global.AboutNewTab.override);
});
});
describe("#destroyChannel", () => {
let channel;
beforeEach(() => {
mm.createChannel();
channel = mm.channel;
});
it("should call channel.destroy()", () => {
mm.destroyChannel();
assert.calledOnce(channel.destroy);
});
it("should set .channel to null", () => {
mm.destroyChannel();
assert.isNull(mm.channel);
});
it("should reset AboutNewTab", () => {
mm.destroyChannel();
assert.calledOnce(global.AboutNewTab.reset);
});
it("should not reset AboutNewTab if the pageURL is not about:newtab", () => {
mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
mm.createChannel();
mm.destroyChannel();
assert.notCalled(global.AboutNewTab.reset);
});
});
});
describe("Message handling", () => {
describe("#getTargetById", () => {
it("should get an id if it exists", () => {
const t = {portID: "foo"};
mm.createChannel();
mm.channel.messagePorts.push(t);
assert.equal(mm.getTargetById("foo"), t);
});
it("should return null if the target doesn't exist", () => {
const t = {portID: "foo"};
mm.createChannel();
mm.channel.messagePorts.push(t);
assert.equal(mm.getTargetById("bar"), null);
});
});
describe("#onNewTabLoad", () => {
it("should dispatch a NEW_TAB_LOAD action", () => {
const t = {portID: "foo"};
sinon.stub(mm, "onActionFromContent");
mm.onNewTabLoad({target: t});
assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_LOAD}, "foo");
});
});
describe("#onNewTabUnload", () => {
it("should dispatch a NEW_TAB_UNLOAD action", () => {
const t = {portID: "foo"};
sinon.stub(mm, "onActionFromContent");
mm.onNewTabUnload({target: t});
assert.calledWith(mm.onActionFromContent, {type: at.NEW_TAB_UNLOAD}, "foo");
});
});
describe("#onMessage", () => {
it("should report an error if the msg.data is missing", () => {
mm.onMessage({target: {portID: "foo"}});
assert.calledOnce(global.Components.utils.reportError);
});
it("should report an error if the msg.data.type is missing", () => {
mm.onMessage({target: {portID: "foo"}, data: "foo"});
assert.calledOnce(global.Components.utils.reportError);
});
it("should call onActionFromContent", () => {
sinon.stub(mm, "onActionFromContent");
const action = {data: {data: {}, type: "FOO"}, target: {portID: "foo"}};
const expectedAction = {
type: action.data.type,
data: action.data.data,
_target: {portID: "foo"}
};
mm.onMessage(action);
assert.calledWith(mm.onActionFromContent, expectedAction, "foo");
});
});
});
describe("Sending and broadcasting", () => {
describe("#send", () => {
it("should send a message on the right port", () => {
const t = {portID: "foo", sendAsyncMessage: sinon.spy()};
mm.createChannel();
mm.channel.messagePorts = [t];
const action = ac.SendToContent({type: "HELLO"}, "foo");
mm.send(action, "foo");
assert.calledWith(t.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
});
it("should not throw if the target isn't around", () => {
mm.createChannel();
// port is not added to the channel
const action = ac.SendToContent({type: "HELLO"}, "foo");
assert.doesNotThrow(() => mm.send(action, "foo"));
});
});
describe("#broadcast", () => {
it("should send a message on the channel", () => {
mm.createChannel();
const action = ac.BroadcastToContent({type: "HELLO"});
mm.broadcast(action);
assert.calledWith(mm.channel.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
});
});
});
describe("Handling actions", () => {
describe("#onActionFromContent", () => {
beforeEach(() => mm.onActionFromContent({type: "FOO"}, "foo"));
it("should dispatch a SendToMain action", () => {
assert.calledOnce(dispatch);
const action = dispatch.firstCall.args[0];
assert.equal(action.type, "FOO", "action.type");
});
it("should have the right fromTarget", () => {
const action = dispatch.firstCall.args[0];
assert.equal(action.meta.fromTarget, "foo", "meta.fromTarget");
});
});
describe("#middleware", () => {
let store;
beforeEach(() => {
store = createStore(addNumberReducer, applyMiddleware(mm.middleware));
});
it("should just call next if no channel is found", () => {
store.dispatch({type: "ADD", data: 10});
assert.equal(store.getState(), 10);
});
it("should call .send if the action is SendToContent", () => {
sinon.stub(mm, "send");
const action = ac.SendToContent({type: "FOO"}, "foo");
mm.createChannel();
store.dispatch(action);
assert.calledWith(mm.send, action);
});
it("should call .broadcast if the action is BroadcastToContent", () => {
sinon.stub(mm, "broadcast");
const action = ac.BroadcastToContent({type: "FOO"});
mm.createChannel();
store.dispatch(action);
assert.calledWith(mm.broadcast, action);
});
it("should dispatch other actions normally", () => {
sinon.stub(mm, "send");
sinon.stub(mm, "broadcast");
mm.createChannel();
store.dispatch({type: "ADD", data: 1});
assert.equal(store.getState(), 1);
assert.notCalled(mm.send);
assert.notCalled(mm.broadcast);
});
});
});
});

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

@ -0,0 +1,77 @@
"use strict";
const {SearchFeed} = require("lib/SearchFeed.jsm");
const {GlobalOverrider} = require("test/unit/utils");
const {actionTypes: at} = require("common/Actions.jsm");
const fakeEngines = [{name: "Google", iconBuffer: "icon.ico"}];
describe("Search Feed", () => {
let feed;
let globals;
before(() => {
globals = new GlobalOverrider();
globals.set("ContentSearch", {
currentStateObj: globals.sandbox.spy(() => Promise.resolve({engines: fakeEngines, currentEngine: {}})),
performSearch: globals.sandbox.spy((browser, searchData) => Promise.resolve({browser, searchData}))
});
});
beforeEach(() => {
feed = new SearchFeed();
feed.store = {dispatch: sinon.spy()};
});
afterEach(() => globals.reset());
after(() => globals.restore());
it("should call get state (with true) from the content search provider on INIT", () => {
feed.onAction({type: at.INIT});
// calling currentStateObj with 'true' allows us to return a data uri for the
// icon, instead of an array buffer
assert.calledWith(global.ContentSearch.currentStateObj, true);
});
it("should get the the state on INIT", () => {
sinon.stub(feed, "getState");
feed.onAction({type: at.INIT});
assert.calledOnce(feed.getState);
});
it("should add observers on INIT", () => {
sinon.stub(feed, "addObservers");
feed.onAction({type: at.INIT});
assert.calledOnce(feed.addObservers);
});
it("should remove observers on UNINIT", () => {
sinon.stub(feed, "removeObservers");
feed.onAction({type: at.UNINIT});
assert.calledOnce(feed.removeObservers);
});
it("should call services.obs.addObserver on INIT", () => {
feed.onAction({type: at.INIT});
assert.calledOnce(global.Services.obs.addObserver);
});
it("should call services.obs.removeObserver on UNINIT", () => {
feed.onAction({type: at.UNINIT});
assert.calledOnce(global.Services.obs.removeObserver);
});
it("should dispatch one event with the state", () => (
feed.getState().then(() => {
assert.calledOnce(feed.store.dispatch);
})
));
it("should perform a search on PERFORM_SEARCH", () => {
sinon.stub(feed, "performSearch");
feed.onAction({_target: {browser: {}}, type: at.PERFORM_SEARCH});
assert.calledOnce(feed.performSearch);
});
it("should call performSearch with an action", () => {
const action = {_target: {browser: "browser"}, data: {searchString: "hello"}};
feed.performSearch(action._target.browser, action.data);
assert.calledWith(global.ContentSearch.performSearch, {target: action._target.browser}, action.data);
});
it("should get the state if we change the search engines", () => {
sinon.stub(feed, "getState");
feed.observe(null, "browser-search-engine-modified", "engine-current");
assert.calledOnce(feed.getState);
});
it("shouldn't get the state if it's not the right notification", () => {
sinon.stub(feed, "getState");
feed.observe(null, "some-other-notification", "engine-current");
assert.notCalled(feed.getState);
});
});

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

@ -0,0 +1,210 @@
const injector = require("inject!lib/Store.jsm");
const {createStore} = require("redux");
const {addNumberReducer} = require("test/unit/utils");
const {GlobalOverrider} = require("test/unit/utils");
describe("Store", () => {
let Store;
let Preferences;
let sandbox;
let store;
let globals;
let PREF_PREFIX;
beforeEach(() => {
globals = new GlobalOverrider();
sandbox = globals.sandbox;
Preferences = new Map();
Preferences.observe = sandbox.spy();
Preferences.ignore = sandbox.spy();
globals.set("Preferences", Preferences);
function ActivityStreamMessageChannel(options) {
this.dispatch = options.dispatch;
this.createChannel = sandbox.spy();
this.destroyChannel = sandbox.spy();
this.middleware = sandbox.spy(s => next => action => next(action));
}
({Store, PREF_PREFIX} = injector({"lib/ActivityStreamMessageChannel.jsm": {ActivityStreamMessageChannel}}));
store = new Store();
});
afterEach(() => {
Preferences.clear();
globals.restore();
});
it("should have an .feeds property that is a Map", () => {
assert.instanceOf(store.feeds, Map);
assert.equal(store.feeds.size, 0, ".feeds.size");
});
it("should have a redux store at ._store", () => {
assert.ok(store._store);
assert.property(store, "dispatch");
assert.property(store, "getState");
});
it("should create a ActivityStreamMessageChannel with the right dispatcher", () => {
assert.ok(store._messageChannel);
assert.equal(store._messageChannel.dispatch, store.dispatch);
});
it("should connect the ActivityStreamMessageChannel's middleware", () => {
store.dispatch({type: "FOO"});
assert.calledOnce(store._messageChannel.middleware);
});
describe("#initFeed", () => {
it("should add an instance of the feed to .feeds", () => {
class Foo {}
Preferences.set(`${PREF_PREFIX}foo`, false);
store.init({foo: () => new Foo()});
store.initFeed("foo");
assert.isTrue(store.feeds.has("foo"), "foo is set");
assert.instanceOf(store.feeds.get("foo"), Foo);
});
it("should add a .store property to the feed", () => {
class Foo {}
store._feedFactories = {foo: () => new Foo()};
store.initFeed("foo");
assert.propertyVal(store.feeds.get("foo"), "store", store);
});
});
describe("#uninitFeed", () => {
it("should not throw if no feed with that name exists", () => {
assert.doesNotThrow(() => {
store.uninitFeed("bar");
});
});
it("should call the feed's uninit function if it is defined", () => {
let feed;
function createFeed() {
feed = {uninit: sinon.spy()};
return feed;
}
store._feedFactories = {foo: createFeed};
store.initFeed("foo");
store.uninitFeed("foo");
assert.calledOnce(feed.uninit);
});
it("should remove the feed from .feeds", () => {
class Foo {}
store._feedFactories = {foo: () => new Foo()};
store.initFeed("foo");
store.uninitFeed("foo");
assert.isFalse(store.feeds.has("foo"), "foo is not in .feeds");
});
});
describe("maybeStartFeedAndListenForPrefChanges", () => {
beforeEach(() => {
sinon.stub(store, "initFeed");
sinon.stub(store, "uninitFeed");
});
it("should set the new pref in Preferences to true, if it was never defined", () => {
store.maybeStartFeedAndListenForPrefChanges("foo");
assert.isTrue(Preferences.get(`${PREF_PREFIX}foo`));
});
it("should not override the pref if it was already set", () => {
Preferences.set(`${PREF_PREFIX}foo`, false);
store.maybeStartFeedAndListenForPrefChanges("foo");
assert.isFalse(Preferences.get(`${PREF_PREFIX}foo`));
});
it("should initialize the feed if the Pref is set to true", () => {
Preferences.set(`${PREF_PREFIX}foo`, true);
store.maybeStartFeedAndListenForPrefChanges("foo");
assert.calledWith(store.initFeed, "foo");
});
it("should not initialize the feed if the Pref is set to false", () => {
Preferences.set(`${PREF_PREFIX}foo`, false);
store.maybeStartFeedAndListenForPrefChanges("foo");
assert.notCalled(store.initFeed);
});
it("should observe the pref", () => {
store.maybeStartFeedAndListenForPrefChanges("foo");
assert.calledWith(Preferences.observe, `${PREF_PREFIX}foo`, store._prefHandlers.get(`${PREF_PREFIX}foo`));
});
describe("handler", () => {
let handler;
beforeEach(() => {
store.maybeStartFeedAndListenForPrefChanges("foo");
handler = store._prefHandlers.get(`${PREF_PREFIX}foo`);
});
it("should initialize the feed if called with true", () => {
handler(true);
assert.calledWith(store.initFeed, "foo");
});
it("should uninitialize the feed if called with false", () => {
handler(false);
assert.calledWith(store.uninitFeed, "foo");
});
});
});
describe("#init", () => {
it("should call .maybeStartFeedAndListenForPrefChanges with each key", () => {
sinon.stub(store, "maybeStartFeedAndListenForPrefChanges");
store.init({foo: () => {}, bar: () => {}});
assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "foo");
assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "bar");
});
it("should initialize the ActivityStreamMessageChannel channel", () => {
store.init();
assert.calledOnce(store._messageChannel.createChannel);
});
});
describe("#uninit", () => {
it("should clear .feeds, ._prefHandlers, and ._feedFactories", () => {
store.init({
a: () => ({}),
b: () => ({}),
c: () => ({})
});
store.uninit();
assert.equal(store.feeds.size, 0);
assert.equal(store._prefHandlers.size, 0);
assert.isNull(store._feedFactories);
});
it("should destroy the ActivityStreamMessageChannel channel", () => {
store.uninit();
assert.calledOnce(store._messageChannel.destroyChannel);
});
});
describe("#getState", () => {
it("should return the redux state", () => {
store._store = createStore((prevState = 123) => prevState);
const {getState} = store;
assert.equal(getState(), 123);
});
});
describe("#dispatch", () => {
it("should call .onAction of each feed", () => {
const {dispatch} = store;
const sub = {onAction: sinon.spy()};
const action = {type: "FOO"};
store.init({sub: () => sub});
dispatch(action);
assert.calledWith(sub.onAction, action);
});
it("should call the reducers", () => {
const {dispatch} = store;
store._store = createStore(addNumberReducer);
dispatch({type: "ADD", data: 14});
assert.equal(store.getState(), 14);
});
});
describe("#subscribe", () => {
it("should subscribe to changes to the store", () => {
const sub = sinon.spy();
const action = {type: "FOO"};
store.subscribe(sub);
store.dispatch(action);
assert.calledOnce(sub);
});
});
});

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

@ -0,0 +1,271 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
const {GlobalOverrider, FakePrefs} = require("test/unit/utils");
const {TelemetrySender} = require("lib/TelemetrySender.jsm");
/**
* A reference to the fake preferences object created by the TelemetrySender
* constructor so that we can use the API.
*/
let fakePrefs;
const prefInitHook = function() {
fakePrefs = this; // eslint-disable-line consistent-this
};
const tsArgs = {prefInitHook};
describe("TelemetrySender", () => {
let globals;
let tSender;
let fetchStub;
const observerTopics = ["user-action-event", "performance-event",
"tab-session-complete", "undesired-event"];
const fakeEndpointUrl = "http://127.0.0.1/stuff";
const fakePingJSON = JSON.stringify({action: "fake_action", monkey: 1});
const fakeFetchHttpErrorResponse = {ok: false, status: 400};
const fakeFetchSuccessResponse = {ok: true, status: 200};
function assertNotificationObserversAdded() {
observerTopics.forEach(topic => {
assert.calledWithExactly(
global.Services.obs.addObserver, tSender, topic, true);
});
}
function assertNotificationObserversRemoved() {
observerTopics.forEach(topic => {
assert.calledWithExactly(
global.Services.obs.removeObserver, tSender, topic);
});
}
before(() => {
globals = new GlobalOverrider();
fetchStub = globals.sandbox.stub();
globals.set("Preferences", FakePrefs);
globals.set("fetch", fetchStub);
});
beforeEach(() => {
});
afterEach(() => {
globals.reset();
FakePrefs.prototype.prefs = {};
});
after(() => globals.restore());
it("should construct the Prefs object with the right branch", () => {
globals.sandbox.spy(global, "Preferences");
tSender = new TelemetrySender(tsArgs);
assert.calledOnce(global.Preferences);
assert.calledWith(global.Preferences,
sinon.match.has("branch", "browser.newtabpage.activity-stream"));
});
it("should set the enabled prop to false if the pref is false", () => {
FakePrefs.prototype.prefs = {telemetry: false};
tSender = new TelemetrySender(tsArgs);
assert.isFalse(tSender.enabled);
});
it("should not add notification observers if the enabled pref is false", () => {
FakePrefs.prototype.prefs = {telemetry: false};
tSender = new TelemetrySender(tsArgs);
assert.notCalled(global.Services.obs.addObserver);
});
it("should set the enabled prop to true if the pref is true", () => {
FakePrefs.prototype.prefs = {telemetry: true};
tSender = new TelemetrySender(tsArgs);
assert.isTrue(tSender.enabled);
});
it("should add all notification observers if the enabled pref is true", () => {
FakePrefs.prototype.prefs = {telemetry: true};
tSender = new TelemetrySender(tsArgs);
assertNotificationObserversAdded();
});
describe("#_sendPing()", () => {
beforeEach(() => {
FakePrefs.prototype.prefs = {
"telemetry": true,
"telemetry.ping.endpoint": fakeEndpointUrl
};
tSender = new TelemetrySender(tsArgs);
});
it("should POST given ping data to telemetry.ping.endpoint pref w/fetch",
async () => {
fetchStub.resolves(fakeFetchSuccessResponse);
await tSender._sendPing(fakePingJSON);
assert.calledOnce(fetchStub);
assert.calledWithExactly(fetchStub, fakeEndpointUrl,
{method: "POST", body: fakePingJSON});
});
it("should log HTTP failures using Cu.reportError", async () => {
fetchStub.resolves(fakeFetchHttpErrorResponse);
await tSender._sendPing(fakePingJSON);
assert.called(Components.utils.reportError);
});
it("should log an error using Cu.reportError if fetch rejects", async () => {
fetchStub.rejects("Oh noes!");
await tSender._sendPing(fakePingJSON);
assert.called(Components.utils.reportError);
});
it("should log if logging is on && if action is not activity_stream_performance", async () => {
FakePrefs.prototype.prefs = {
"telemetry": true,
"performance.log": true
};
fetchStub.resolves(fakeFetchSuccessResponse);
tSender = new TelemetrySender(tsArgs);
await tSender._sendPing(fakePingJSON);
assert.called(console.log); // eslint-disable-line no-console
});
});
describe("#observe()", () => {
before(() => {
globals.sandbox.stub(TelemetrySender.prototype, "_sendPing");
});
observerTopics.forEach(topic => {
it(`should call this._sendPing with data for ${topic}`, () => {
const fakeSubject = "fakeSubject";
tSender = new TelemetrySender(tsArgs);
tSender.observe(fakeSubject, topic, fakePingJSON);
assert.calledOnce(TelemetrySender.prototype._sendPing);
assert.calledWithExactly(TelemetrySender.prototype._sendPing,
fakePingJSON);
});
});
it("should not call this._sendPing for 'nonexistent-topic'", () => {
const fakeSubject = "fakeSubject";
tSender = new TelemetrySender(tsArgs);
tSender.observe(fakeSubject, "nonexistent-topic", fakePingJSON);
assert.notCalled(TelemetrySender.prototype._sendPing);
});
});
describe("#uninit()", () => {
it("should remove the telemetry pref listener", () => {
tSender = new TelemetrySender(tsArgs);
assert.property(fakePrefs.observers, "telemetry");
tSender.uninit();
assert.notProperty(fakePrefs.observers, "telemetry");
});
it("should remove all notification observers if telemetry pref is true", () => {
FakePrefs.prototype.prefs = {telemetry: true};
tSender = new TelemetrySender(tsArgs);
tSender.uninit();
assertNotificationObserversRemoved();
});
it("should not remove notification observers if telemetry pref is false", () => {
FakePrefs.prototype.prefs = {telemetry: false};
tSender = new TelemetrySender(tsArgs);
tSender.uninit();
assert.notCalled(global.Services.obs.removeObserver);
});
it("should call Cu.reportError if this._prefs.ignore throws", () => {
globals.sandbox.stub(FakePrefs.prototype, "ignore").throws("Some Error");
tSender = new TelemetrySender(tsArgs);
tSender.uninit();
assert.called(global.Components.utils.reportError);
});
});
describe("Misc pref changes", () => {
describe("telemetry changes from true to false", () => {
beforeEach(() => {
FakePrefs.prototype.prefs = {"telemetry": true};
tSender = new TelemetrySender(tsArgs);
assert.propertyVal(tSender, "enabled", true);
});
it("should set the enabled property to false", () => {
fakePrefs.set("telemetry", false);
assert.propertyVal(tSender, "enabled", false);
});
it("should remove all notification observers", () => {
fakePrefs.set("telemetry", false);
assertNotificationObserversRemoved();
});
});
describe("telemetry changes from false to true", () => {
beforeEach(() => {
FakePrefs.prototype.prefs = {"telemetry": false};
tSender = new TelemetrySender(tsArgs);
assert.propertyVal(tSender, "enabled", false);
});
it("should set the enabled property to true", () => {
fakePrefs.set("telemetry", true);
assert.propertyVal(tSender, "enabled", true);
});
it("should add all topic observers", () => {
fakePrefs.set("telemetry", true);
assertNotificationObserversAdded();
});
});
describe("performance.log changes from false to true", () => {
it("should change this.logging from false to true", () => {
FakePrefs.prototype.prefs = {"performance.log": false};
tSender = new TelemetrySender(tsArgs);
assert.propertyVal(tSender, "logging", false);
fakePrefs.set("performance.log", true);
assert.propertyVal(tSender, "logging", true);
});
});
});
});

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

@ -0,0 +1,116 @@
"use strict";
const {TopSitesFeed, UPDATE_TIME, TOP_SITES_SHOWMORE_LENGTH, DEFAULT_TOP_SITES} = require("lib/TopSitesFeed.jsm");
const {GlobalOverrider} = require("test/unit/utils");
const action = {meta: {fromTarget: {}}};
const {actionTypes: at} = require("common/Actions.jsm");
const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({url: `site${i}.com`}));
const FAKE_SCREENSHOT = "data123";
describe("Top Sites Feed", () => {
let feed;
let globals;
let sandbox;
let links;
let clock;
before(() => {
globals = new GlobalOverrider();
sandbox = globals.sandbox;
});
beforeEach(() => {
globals.set("PlacesProvider", {links: {getLinks: sandbox.spy(() => Promise.resolve(links))}});
globals.set("PreviewProvider", {getThumbnail: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT))});
feed = new TopSitesFeed();
feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }};
links = FAKE_LINKS;
clock = sinon.useFakeTimers();
});
afterEach(() => {
globals.restore();
clock.restore();
});
it("should have default sites with .isDefault = true", () => {
DEFAULT_TOP_SITES.forEach(link => assert.propertyVal(link, "isDefault", true));
});
describe("#getLinksWithDefaults", () => {
it("should get the links from Places Provider", async () => {
const result = await feed.getLinksWithDefaults();
assert.deepEqual(result, links);
assert.calledOnce(global.PlacesProvider.links.getLinks);
});
it("should add defaults if there are are not enough links", async () => {
links = [{url: "foo.com"}];
const result = await feed.getLinksWithDefaults();
assert.deepEqual(result, [{url: "foo.com"}, ...DEFAULT_TOP_SITES]);
});
it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
links = new Array(TOP_SITES_SHOWMORE_LENGTH - 1).fill({url: "foo.com"});
const result = await feed.getLinksWithDefaults();
assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
assert.deepEqual(result, [...links, DEFAULT_TOP_SITES[0]]);
});
it("should not throw if PlacesProvider returns null", () => {
links = null;
assert.doesNotThrow(() => {
feed.getLinksWithDefaults(action);
});
});
});
describe("#refresh", () => {
it("should dispatch an action with the links returned", async () => {
sandbox.stub(feed, "getScreenshot");
await feed.refresh(action);
assert.calledOnce(feed.store.dispatch);
assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, links);
});
it("should call .getScreenshot for each link", async () => {
sandbox.stub(feed, "getScreenshot");
await feed.refresh(action);
links.forEach(link => assert.calledWith(feed.getScreenshot, link.url));
});
});
describe("getScreenshot", () => {
it("should call PreviewProvider.getThumbnail with the right url", async () => {
const url = "foo.com";
await feed.getScreenshot(url);
assert.calledWith(global.PreviewProvider.getThumbnail, url);
});
});
describe("#onAction", () => {
it("should call refresh if there are not enough sites on NEW_TAB_LOAD", () => {
feed.store.getState = function() { return {TopSites: {rows: []}}; };
sinon.stub(feed, "refresh");
feed.onAction({type: at.NEW_TAB_LOAD});
assert.calledOnce(feed.refresh);
});
it("should call refresh if there are not sites on NEW_TAB_LOAD, not counting defaults", () => {
feed.store.getState = function() { return {TopSites: {rows: [{url: "foo.com"}, ...DEFAULT_TOP_SITES]}}; };
sinon.stub(feed, "refresh");
feed.onAction({type: at.NEW_TAB_LOAD});
assert.calledOnce(feed.refresh);
});
it("should not call refresh if there are enough sites on NEW_TAB_LOAD", () => {
feed.lastUpdated = Date.now();
sinon.stub(feed, "refresh");
feed.onAction({type: at.NEW_TAB_LOAD});
assert.notCalled(feed.refresh);
});
it("should call refresh if .lastUpdated is too old on NEW_TAB_LOAD", () => {
feed.lastUpdated = 0;
clock.tick(UPDATE_TIME);
sinon.stub(feed, "refresh");
feed.onAction({type: at.NEW_TAB_LOAD});
assert.calledOnce(feed.refresh);
});
it("should not call refresh if .lastUpdated is less than update time on NEW_TAB_LOAD", () => {
feed.lastUpdated = 0;
clock.tick(UPDATE_TIME - 1);
sinon.stub(feed, "refresh");
feed.onAction({type: at.NEW_TAB_LOAD});
assert.notCalled(feed.refresh);
});
});
});

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

@ -0,0 +1,43 @@
const initStore = require("content-src/lib/init-store");
const {GlobalOverrider, addNumberReducer} = require("test/unit/utils");
const {actionCreators: ac} = require("common/Actions.jsm");
describe("initStore", () => {
let globals;
let store;
before(() => {
globals = new GlobalOverrider();
globals.set("sendAsyncMessage", globals.sandbox.spy());
globals.set("addMessageListener", globals.sandbox.spy());
});
beforeEach(() => {
store = initStore({number: addNumberReducer});
});
afterEach(() => globals.reset());
after(() => globals.restore());
it("should create a store with the provided reducers", () => {
assert.ok(store);
assert.property(store.getState(), "number");
});
it("should add a listener for incoming actions", () => {
assert.calledWith(global.addMessageListener, initStore.INCOMING_MESSAGE_NAME);
const callback = global.addMessageListener.firstCall.args[1];
globals.sandbox.spy(store, "dispatch");
const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
callback(message);
assert.calledWith(store.dispatch, message.data);
});
it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
store.dispatch({type: initStore.MERGE_STORE_ACTION, data: {number: 42}});
assert.deepEqual(store.getState(), {number: 42});
});
it("should send out SendToMain ations", () => {
const action = ac.SendToMain({type: "FOO"});
store.dispatch(action);
assert.calledWith(global.sendAsyncMessage, initStore.OUTGOING_MESSAGE_NAME, action);
});
it("should not send out other types of ations", () => {
store.dispatch({type: "FOO"});
assert.notCalled(global.sendAsyncMessage);
});
});

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

@ -0,0 +1,38 @@
const {GlobalOverrider} = require("test/unit/utils");
const req = require.context(".", true, /\.test\.js$/);
const files = req.keys();
// This exposes sinon assertions to chai.assert
sinon.assert.expose(assert, {prefix: ""});
let overrider = new GlobalOverrider();
overrider.set({
Components: {
interfaces: {},
utils: {
import: overrider.sandbox.spy(),
importGlobalProperties: overrider.sandbox.spy(),
reportError: overrider.sandbox.spy()
}
},
XPCOMUtils: {
defineLazyModuleGetter: overrider.sandbox.spy(),
defineLazyServiceGetter: overrider.sandbox.spy(),
generateQI: overrider.sandbox.stub().returns(() => {})
},
console: {log: overrider.sandbox.spy()},
dump: overrider.sandbox.spy(),
Services: {
obs: {
addObserver: overrider.sandbox.spy(),
removeObserver: overrider.sandbox.spy()
}
}
});
describe("activity-stream", () => {
afterEach(() => overrider.reset());
after(() => overrider.restore());
files.forEach(file => req(file));
});

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

@ -0,0 +1,122 @@
/**
* GlobalOverrider - Utility that allows you to override properties on the global object.
* See unit-entry.js for example usage.
*/
class GlobalOverrider {
constructor() {
this.originalGlobals = new Map();
this.sandbox = sinon.sandbox.create();
}
/**
* _override - Internal method to override properties on the global object.
* The first time a given key is overridden, we cache the original
* value in this.originalGlobals so that later it can be restored.
*
* @param {string} key The identifier of the property
* @param {any} value The value to which the property should be reassigned
*/
_override(key, value) {
if (key === "Components") {
// Components can be reassigned, but it will subsequently throw a deprecation
// error in Firefox which will stop execution. Adding the assignment statement
// to a try/catch block will prevent this from happening.
try {
global[key] = value;
} catch (e) {} // eslint-disable-line no-empty
return;
}
if (!this.originalGlobals.has(key)) {
this.originalGlobals.set(key, global[key]);
}
global[key] = value;
}
/**
* set - Override a given property, or all properties on an object
*
* @param {string|object} key If a string, the identifier of the property
* If an object, a number of properties and values to which they should be reassigned.
* @param {any} value The value to which the property should be reassigned
* @return {type} description
*/
set(key, value) {
if (!value && typeof key === "object") {
const overrides = key;
Object.keys(overrides).forEach(k => this._override(k, overrides[k]));
} else {
this._override(key, value);
}
}
/**
* reset - Reset the global sandbox, so all state on spies, stubs etc. is cleared.
* You probably want to call this after each test.
*/
reset() {
this.sandbox.reset();
}
/**
* restore - Restore the global sandbox and reset all overriden properties to
* their original values. You should call this after all tests have completed.
*/
restore() {
this.sandbox.restore();
this.originalGlobals.forEach((value, key) => {
global[key] = value;
});
}
}
/**
* Very simple fake for the most basic semantics of Preferences.jsm. Lots of
* things aren't yet supported. Feel free to add them in.
*
* @param {Object} args - optional arguments
* @param {Function} args.initHook - if present, will be called back
* inside the constructor. Typically used from tests
* to save off a pointer to the created instance so that
* stubs and spies can be inspected by the test code.
*/
function FakePrefs(args) {
if (args) {
if ("initHook" in args) {
args.initHook.call(this);
}
}
}
FakePrefs.prototype = {
observers: {},
observe(prefName, callback) {
this.observers[prefName] = callback;
},
ignore(prefName, callback) {
if (prefName in this.observers) {
delete this.observers[prefName];
}
},
prefs: {},
get(prefName) { return this.prefs[prefName]; },
set(prefName, value) {
this.prefs[prefName] = value;
if (prefName in this.observers) {
this.observers[prefName](value);
}
}
};
/**
* addNumberReducer - a simple dummy reducer for testing that adds a number
*/
function addNumberReducer(prevState = 0, action) {
return action.type === "ADD" ? prevState + action.data : prevState;
}
module.exports = {
FakePrefs,
GlobalOverrider,
addNumberReducer
};

948
browser/extensions/activity-stream/vendor/redux.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,948 @@
/**
* Redux v.3.6.0
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["Redux"] = factory();
else
root["Redux"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined;
var _createStore = __webpack_require__(2);
var _createStore2 = _interopRequireDefault(_createStore);
var _combineReducers = __webpack_require__(7);
var _combineReducers2 = _interopRequireDefault(_combineReducers);
var _bindActionCreators = __webpack_require__(6);
var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
var _applyMiddleware = __webpack_require__(5);
var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
var _compose = __webpack_require__(1);
var _compose2 = _interopRequireDefault(_compose);
var _warning = __webpack_require__(3);
var _warning2 = _interopRequireDefault(_warning);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
if (("development") !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
(0, _warning2['default'])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
}
exports.createStore = _createStore2['default'];
exports.combineReducers = _combineReducers2['default'];
exports.bindActionCreators = _bindActionCreators2['default'];
exports.applyMiddleware = _applyMiddleware2['default'];
exports.compose = _compose2['default'];
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
exports.__esModule = true;
exports["default"] = compose;
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
var last = funcs[funcs.length - 1];
var rest = funcs.slice(0, -1);
return function () {
return rest.reduceRight(function (composed, f) {
return f(composed);
}, last.apply(undefined, arguments));
};
}
/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports.ActionTypes = undefined;
exports['default'] = createStore;
var _isPlainObject = __webpack_require__(4);
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
var _symbolObservable = __webpack_require__(12);
var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var ActionTypes = exports.ActionTypes = {
INIT: '@@redux/INIT'
};
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param {Function} enhancer The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing âœwhat changedâ. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!(0, _isPlainObject2['default'])(action)) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/zenparsing/es-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.');
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return { unsubscribe: unsubscribe };
}
}, _ref[_symbolObservable2['default']] = function () {
return this;
}, _ref;
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT });
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[_symbolObservable2['default']] = observable, _ref2;
}
/***/ },
/* 3 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
exports['default'] = warning;
/**
* Prints a warning in the console if it exists.
*
* @param {String} message The warning message.
* @returns {void}
*/
function warning(message) {
/* eslint-disable no-console */
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message);
}
/* eslint-enable no-console */
try {
// This error was thrown as a convenience so that if you enable
// "break on all exceptions" in your console,
// it would pause the execution at this line.
throw new Error(message);
/* eslint-disable no-empty */
} catch (e) {}
/* eslint-enable no-empty */
}
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
var getPrototype = __webpack_require__(8),
isHostObject = __webpack_require__(9),
isObjectLike = __webpack_require__(11);
/** `Object#toString` result references. */
var objectTag = '[object Object]';
/** Used for built-in method references. */
var funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to infer the `Object` constructor. */
var objectCtorString = funcToString.call(Object);
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString = objectProto.toString;
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!isObjectLike(value) ||
objectToString.call(value) != objectTag || isHostObject(value)) {
return false;
}
var proto = getPrototype(value);
if (proto === null) {
return true;
}
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return (typeof Ctor == 'function' &&
Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
}
module.exports = isPlainObject;
/***/ },
/* 5 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports['default'] = applyMiddleware;
var _compose = __webpack_require__(1);
var _compose2 = _interopRequireDefault(_compose);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
};
chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
/***/ },
/* 6 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
exports['default'] = bindActionCreators;
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(undefined, arguments));
};
}
/**
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
*
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
*
* @param {Function|Object} actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
*
* @param {Function} dispatch The `dispatch` function available on your Redux
* store.
*
* @returns {Function|Object} The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
*/
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
}
var keys = Object.keys(actionCreators);
var boundActionCreators = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
/***/ },
/* 7 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports['default'] = combineReducers;
var _createStore = __webpack_require__(2);
var _isPlainObject = __webpack_require__(4);
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
var _warning = __webpack_require__(3);
var _warning2 = _interopRequireDefault(_warning);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function getUndefinedStateErrorMessage(key, action) {
var actionType = action && action.type;
var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.';
}
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
var reducerKeys = Object.keys(reducers);
var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
if (reducerKeys.length === 0) {
return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
}
if (!(0, _isPlainObject2['default'])(inputState)) {
return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
}
var unexpectedKeys = Object.keys(inputState).filter(function (key) {
return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
});
unexpectedKeys.forEach(function (key) {
unexpectedKeyCache[key] = true;
});
if (unexpectedKeys.length > 0) {
return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
}
}
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT });
if (typeof initialState === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.');
}
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
if (typeof reducer(undefined, { type: type }) === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.');
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (true) {
if (typeof reducers[key] === 'undefined') {
(0, _warning2['default'])('No reducer provided for key "' + key + '"');
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers);
if (true) {
var unexpectedKeyCache = {};
}
var sanityError;
try {
assertReducerSanity(finalReducers);
} catch (e) {
sanityError = e;
}
return function combination() {
var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var action = arguments[1];
if (sanityError) {
throw sanityError;
}
if (true) {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
(0, _warning2['default'])(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i];
var reducer = finalReducers[key];
var previousStateForKey = state[key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action);
throw new Error(errorMessage);
}
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
/***/ },
/* 8 */
/***/ function(module, exports, __webpack_require__) {
var overArg = __webpack_require__(10);
/** Built-in value references. */
var getPrototype = overArg(Object.getPrototypeOf, Object);
module.exports = getPrototype;
/***/ },
/* 9 */
/***/ function(module, exports) {
/**
* Checks if `value` is a host object in IE < 9.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
*/
function isHostObject(value) {
// Many host objects are `Object` objects that can coerce to strings
// despite having improperly defined `toString` methods.
var result = false;
if (value != null && typeof value.toString != 'function') {
try {
result = !!(value + '');
} catch (e) {}
}
return result;
}
module.exports = isHostObject;
/***/ },
/* 10 */
/***/ function(module, exports) {
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
module.exports = overArg;
/***/ },
/* 11 */
/***/ function(module, exports) {
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
module.exports = isObjectLike;
/***/ },
/* 12 */
/***/ function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(13);
/***/ },
/* 13 */
/***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(global) {'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ponyfill = __webpack_require__(14);
var _ponyfill2 = _interopRequireDefault(_ponyfill);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var root = undefined; /* global window */
if (typeof global !== 'undefined') {
root = global;
} else if (typeof window !== 'undefined') {
root = window;
}
var result = (0, _ponyfill2['default'])(root);
exports['default'] = result;
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
/***/ },
/* 14 */
/***/ function(module, exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports['default'] = symbolObservablePonyfill;
function symbolObservablePonyfill(root) {
var result;
var _Symbol = root.Symbol;
if (typeof _Symbol === 'function') {
if (_Symbol.observable) {
result = _Symbol.observable;
} else {
result = _Symbol('observable');
_Symbol.observable = result;
}
} else {
result = '@@observable';
}
return result;
};
/***/ }
/******/ ])
});
;

43
browser/extensions/e10srollout/bootstrap.js поставляемый
Просмотреть файл

@ -11,13 +11,19 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/UpdateUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
// The amount of people to be part of e10s
// The amount of people to be part of e10s
const TEST_THRESHOLD = {
"beta": 0.9, // 90%
"release": 1.0, // 100%
"esr": 1.0, // 100%
};
// If a user qualifies for the e10s-multi experiement, this is how many
// content processes to use.
const MULTI_BUCKETS = {
"beta": { 1: .5, 4: 1, },
};
const ADDON_ROLLOUT_POLICY = {
"beta": "50allmpc",
"release": "50allmpc",
@ -120,14 +126,14 @@ function defineCohort() {
cohortPrefix = `addons-set${addonPolicy}-`;
}
let inMultiExperiment = false;
let eligibleForMulti = false;
if (userOptedOut.e10s || userOptedOut.multi) {
// If we detected that the user opted out either for multi or e10s, then
// the proper prefs must already be set.
setCohort("optedOut");
} else if (userOptedIn.e10s) {
setCohort("optedIn");
inMultiExperiment = true;
eligibleForMulti = true;
} else if (temporaryDisqualification != "") {
// Users who are disqualified by the backend (from multiprocessBlockPolicy)
// can be put into either the test or control groups, because e10s will
@ -147,11 +153,11 @@ function defineCohort() {
// qualification which overrides the user sample value when non-empty.
setCohort(`temp-qualified-${temporaryQualification}`);
Preferences.set(PREF_TOGGLE_E10S, true);
inMultiExperiment = true;
eligibleForMulti = true;
} else if (testGroup) {
setCohort(`${cohortPrefix}test`);
Preferences.set(PREF_TOGGLE_E10S, true);
inMultiExperiment = true;
eligibleForMulti = true;
} else {
setCohort(`${cohortPrefix}control`);
Preferences.reset(PREF_TOGGLE_E10S);
@ -159,34 +165,37 @@ function defineCohort() {
}
// Now determine if this user should be in the e10s-multi experiment.
// - We only run the experiment on the beta channel.
// - We only run the experiment on channels defined in MULTI_BUCKETS.
// - We decided above whether this user qualifies for the experiment.
// - If the user already opted into multi, then their prefs are already set
// correctly, we're done.
// - If the user has addons that disqualify them for multi, leave them with
// the default number of content processes (1 on beta) but still in the
// test cohort.
if (updateChannel !== "beta" ||
!inMultiExperiment ||
if (!(updateChannel in MULTI_BUCKETS) ||
!eligibleForMulti ||
userOptedIn.multi ||
disqualified ||
getAddonsDisqualifyForMulti()) {
Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web");
return;
}
// If we got here with a cohortPrefix, it must be "addons-set50allmpc-",
// and we know because of getAddonsDisqualifyForMulti that the addons that
// are installed must be web extensions.
if (cohortPrefix) {
cohortPrefix = "webextensions-";
}
// The user is in the multi experiment!
// Decide how many content processes to use for this user.
let BUCKETS = {
1: .25,
2: .5,
4: .75,
8: 1
};
let buckets = MULTI_BUCKETS[updateChannel];
let multiUserSample = getUserSample(true);
for (let sampleName of Object.getOwnPropertyNames(BUCKETS)) {
if (multiUserSample < BUCKETS[sampleName]) {
setCohort(`multiBucket${sampleName}`);
for (let sampleName of Object.getOwnPropertyNames(buckets)) {
if (multiUserSample < buckets[sampleName]) {
setCohort(`${cohortPrefix}multiBucket${sampleName}`);
Preferences.set(PREF_E10S_PROCESSCOUNT + ".web", sampleName);
break;
}

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

@ -10,7 +10,7 @@
<Description about="urn:mozilla:install-manifest">
<em:id>e10srollout@mozilla.org</em:id>
<em:version>1.15</em:version>
<em:version>1.50</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

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

@ -64,6 +64,8 @@ ManageProfileDialog.prototype = {
loadProfiles() {
return this.getProfiles().then(profiles => {
log.debug("profiles:", profiles);
// Sort by last modified time starting with most recent
profiles.sort((a, b) => b.timeLastModified - a.timeLastModified);
this.renderProfileElements(profiles);
this.updateButtonsStates(this._selectedOptions.length);
});

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

@ -10,7 +10,7 @@
-->
<!ENTITY update.checkForUpdatesButton.label "Check for updates">
<!ENTITY update.checkForUpdatesButton.accesskey "C">
<!ENTITY update.updateButton.label2 "Restart &brandShortName; to Update">
<!ENTITY update.updateButton.label3 "Restart to update &brandShorterName;">
<!ENTITY update.updateButton.accesskey "R">

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

@ -920,7 +920,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY updateManual.panelUI.label "Download a fresh copy of &brandShorterName;">
<!ENTITY updateRestart.message "After a quick restart, &brandShorterName; will restore all your open tabs and windows.">
<!ENTITY updateRestart.header.message "Restart &brandShorterName; to apply the update.">
<!ENTITY updateRestart.header.message2 "Restart to update &brandShorterName;.">
<!ENTITY updateRestart.acceptButton.label "Restart and Restore">
<!ENTITY updateRestart.acceptButton.accesskey "R">
<!ENTITY updateRestart.cancelButton.label "Not Now">

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

@ -763,11 +763,6 @@ customizeTips.tip0.learnMore = Learn more
# LOCALIZATION NOTE (customizeMode.tabTitle): %S is brandShortName
customizeMode.tabTitle = Customize %S
# LOCALIZATION NOTE : FILE Reader View is a feature name and therefore typically used as a proper noun.
readingList.promo.firstUse.readerView.title = Reader View
readingList.promo.firstUse.readerView.body = Remove clutter so you can focus exactly on what you want to read.
# LOCALIZATION NOTE (appMenuRemoteTabs.mobilePromo.text2):
# %1$S will be replaced with a link, the text of which is
# appMenuRemoteTabs.mobilePromo.android and the link will be to

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

@ -5,6 +5,7 @@ ar
as
ast
az
be
bg
bn-BD
bn-IN

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

@ -134,7 +134,7 @@ this.ContentLinkHandler = {
getLinkIconURI(aLink) {
let targetDoc = aLink.ownerDocument;
var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
var uri = Services.io.newURI(aLink.href, targetDoc.characterSet);
try {
uri.userPass = "";
} catch (e) {

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

@ -20,8 +20,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.js
const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
var ReaderParent = {
_readerModeInfoPanelOpen: false,
MESSAGES: [
"Reader:ArticleGet",
"Reader:FaviconRequest",
@ -108,20 +106,6 @@ var ReaderParent = {
command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.enter.accesskey"));
key.setAttribute("disabled", !browser.isArticle);
}
let currentUriHost = browser.currentURI && browser.currentURI.asciiHost;
if (browser.isArticle &&
!Services.prefs.getBoolPref("browser.reader.detectedFirstArticle") &&
currentUriHost && !currentUriHost.endsWith("mozilla.org")) {
this.showReaderModeInfoPanel(browser);
Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", true);
this._readerModeInfoPanelOpen = true;
} else if (this._readerModeInfoPanelOpen) {
if (UITour.isInfoOnTarget(win, "readerMode-urlBar")) {
UITour.hideInfo(win);
}
this._readerModeInfoPanelOpen = false;
}
},
forceShowReaderIcon(browser) {
@ -142,29 +126,6 @@ var ReaderParent = {
browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
},
/**
* Shows an info panel from the UITour for Reader Mode.
*
* @param browser The <browser> that the tour should be started for.
*/
showReaderModeInfoPanel(browser) {
let win = browser.ownerGlobal;
let targetPromise = UITour.getTarget(win, "readerMode-urlBar");
targetPromise.then(target => {
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
let icon = "chrome://browser/skin/";
if (win.devicePixelRatio > 1) {
icon += "reader-tour@2x.png";
} else {
icon += "reader-tour.png";
}
UITour.showInfo(win, target,
browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.title"),
browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.body"),
icon);
});
},
/**
* Gets an article for a given URL. This method will download and parse a document.
*

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

@ -7,6 +7,7 @@ support-files =
[browser_BrowserUITelemetry_sidebar.js]
[browser_BrowserUITelemetry_syncedtabs.js]
[browser_ContentSearch.js]
skip-if = (os == "mac" || os == "linux") # Bug 1308343
support-files =
contentSearch.js
contentSearchBadImage.xml

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

@ -1798,11 +1798,3 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left {
.subviewbutton-iconic > .toolbarbutton-text {
padding-inline-start: 5px;
}
#appMenu-new-window-button {
list-style-image: url(chrome://browser/skin/menu-icons/new-window.svg);
}
#appMenu-private-window-button {
list-style-image: url(chrome://browser/skin/menu-icons/private-window.svg);
}

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

@ -125,8 +125,6 @@
skin/classic/browser/favicon-search-16.svg (../shared/favicon-search-16.svg)
skin/classic/browser/icon-search-64.svg (../shared/incontent-icons/icon-search-64.svg)
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
skin/classic/browser/reader-tour.png (../shared/reader/reader-tour.png)
skin/classic/browser/reader-tour@2x.png (../shared/reader/reader-tour@2x.png)
skin/classic/browser/readerMode.svg (../shared/reader/readerMode.svg)
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
skin/classic/browser/panic-panel/header@2x.png (../shared/panic-panel/header@2x.png)
@ -146,4 +144,5 @@
skin/classic/browser/urlbar-star.svg (../shared/urlbar-star.svg)
skin/classic/browser/urlbar-tab.svg (../shared/urlbar-tab.svg)
skin/classic/browser/menu-icons/new-window.svg (../shared/menu-icons/new-window.svg)
skin/classic/browser/menu-icons/print.svg (../shared/menu-icons/print.svg)
skin/classic/browser/menu-icons/private-window.svg (../shared/menu-icons/private-window.svg)

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

@ -1,3 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="context-fill" d="M14 1H2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h5a1 1 0 0 0 0-2H3a1 1 0 0 1-1-1V6h12v2a1 1 0 0 0 2 0V3a2 2 0 0 0-2-2zm0 4H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm1.5 7H13V9.5a.5.5 0 1 0-1 0V12H9.5a.5.5 0 0 0 0 1H12v2.5a.5.5 0 0 0 1 0V13h2.5a.5.5 0 0 0 0-1z"/>
</svg>

До

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

После

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

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

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="context-fill" d="M14 5h-1V1a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v4H2a2 2 0 0 0-2 2v5h3v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3h3V7a2 2 0 0 0-2-2zM2.5 8a.5.5 0 1 1 .5-.5.5.5 0 0 1-.5.5zm9.5 7H4v-5h8zm0-10H4V1h8zm-6.5 7h4a.5.5 0 0 0 0-1h-4a.5.5 0 1 0 0 1zm0 2h5a.5.5 0 0 0 0-1h-5a.5.5 0 1 0 0 1z"/>
</svg>

После

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

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

@ -1,3 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="context-fill" d="M12.377 11.961c-1.651 0-2.793-1.98-4.377-1.98s-2.824 1.98-4.377 1.98c-2.037 0-3.541-1.924-3.566-5.221-.015-2.047.6-2.7 3.242-2.7S6.719 5.12 8 5.12s2.056-1.08 4.7-1.08 3.257.653 3.242 2.7c-.024 3.297-1.528 5.221-3.565 5.221zM4.6 6.56c-1.607.07-2.269 1.025-2.269 1.26s1.066.9 2.107.9S6.7 8.339 6.7 8a1.889 1.889 0 0 0-2.1-1.44zm6.808 0A1.889 1.889 0 0 0 9.3 8c0 .339 1.228.72 2.269.72s2.107-.665 2.107-.9-.664-1.191-2.276-1.26z"/>
</svg>

До

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

После

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

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

@ -181,3 +181,15 @@ toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-in-button {
-moz-image-region: rect(0px, 96px, 16px, 80px);
}
#appMenu-new-window-button {
list-style-image: url(chrome://browser/skin/menu-icons/new-window.svg);
}
#appMenu-private-window-button {
list-style-image: url(chrome://browser/skin/menu-icons/private-window.svg);
}
#appMenu-print-button {
list-style-image: url(chrome://browser/skin/menu-icons/print.svg);
}

Двоичные данные
browser/themes/shared/reader/reader-tour.png

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

До

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

Двоичные данные
browser/themes/shared/reader/reader-tour@2x.png

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

До

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

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

@ -17,41 +17,62 @@ this.Preferences = {
init(libDir) {
let panes = [
["paneGeneral", null],
["paneSearch", null],
["paneContent", null],
["paneApplications", null],
["panePrivacy", null],
["panePrivacy", null, DNTDialog],
["panePrivacy", null, clearRecentHistoryDialog],
["paneSecurity", null],
["paneSync", null],
["paneAdvanced", "generalTab"],
["paneAdvanced", "dataChoicesTab"],
["paneAdvanced", "networkTab"],
["paneAdvanced", "networkTab", connectionDialog],
["paneAdvanced", "updateTab"],
["paneAdvanced", "encryptionTab"],
["paneAdvanced", "encryptionTab", certManager],
["paneAdvanced", "encryptionTab", deviceManager],
/* The "new" organization */
["paneGeneral"],
["paneGeneral", scrollToBrowsingGroup],
["paneApplications"],
["paneSync"],
["panePrivacy"],
["panePrivacy", scrollToCacheGroup],
["panePrivacy", DNTDialog],
["panePrivacy", clearRecentHistoryDialog],
["panePrivacy", connectionDialog],
["panePrivacy", certManager],
["panePrivacy", deviceManager],
["paneAdvanced"],
/* The "old" organization. The third argument says to
set the pref to show the old organization when
opening the preferences. */
["paneGeneral", null, true],
["paneSearch", null, true],
["paneContent", null, true],
["paneApplications", null, true],
["panePrivacy", null, true],
["panePrivacy", DNTDialog, true],
["panePrivacy", clearRecentHistoryDialog, true],
["paneSecurity", null, true],
["paneSync", null, true],
["paneAdvanced", null, true, "generalTab"],
["paneAdvanced", null, true, "dataChoicesTab"],
["paneAdvanced", null, true, "networkTab"],
["paneAdvanced", connectionDialog, true, "networkTab"],
["paneAdvanced", null, true, "updateTab"],
["paneAdvanced", null, true, "encryptionTab"],
["paneAdvanced", certManager, true, "encryptionTab"],
["paneAdvanced", deviceManager, true, "encryptionTab"],
];
for (let [primary, advanced, customFn] of panes) {
for (let [primary, customFn, useOldOrg, advanced] of panes) {
let configName = primary.replace(/^pane/, "prefs") + (advanced ? "-" + advanced : "");
if (customFn) {
configName += "-" + customFn.name;
}
this.configurations[configName] = {};
this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced, customFn);
this.configurations[configName].applyConfig = prefHelper.bind(null, primary, customFn, useOldOrg, advanced);
}
},
configurations: {},
};
let prefHelper = Task.async(function*(primary, advanced = null, customFn = null) {
let prefHelper = Task.async(function*(primary, customFn = null, useOldOrg = false, advanced = null) {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let selectedBrowser = browserWindow.gBrowser.selectedBrowser;
if (useOldOrg) {
Services.prefs.setBoolPref("browser.preferences.useOldOrganization", !!useOldOrg);
}
// close any dialog that might still be open
yield ContentTask.spawn(selectedBrowser, null, function*() {
if (!content.window.gSubDialog) {
@ -72,7 +93,7 @@ let prefHelper = Task.async(function*(primary, advanced = null, customFn = null)
readyPromise = TestUtils.topicObserved("advanced-pane-loaded");
}
if (primary == "paneAdvanced") {
if (useOldOrg && primary == "paneAdvanced") {
browserWindow.openAdvancedPreferences(advanced);
} else {
browserWindow.openPreferences(primary);
@ -85,6 +106,8 @@ let prefHelper = Task.async(function*(primary, advanced = null, customFn = null)
yield* customFn(selectedBrowser);
yield customPaintPromise;
}
Services.prefs.clearUserPref("browser.preferences.useOldOrganization");
});
function paintPromise(browserWindow) {
@ -95,6 +118,18 @@ function paintPromise(browserWindow) {
});
}
function* scrollToBrowsingGroup(aBrowser) {
yield ContentTask.spawn(aBrowser, null, function* () {
content.document.getElementById("browsingGroup").scrollIntoView();
});
}
function* scrollToCacheGroup(aBrowser) {
yield ContentTask.spawn(aBrowser, null, function* () {
content.document.getElementById("cacheGroup").scrollIntoView();
});
}
function* DNTDialog(aBrowser) {
yield ContentTask.spawn(aBrowser, null, function* () {
content.document.getElementById("doNotTrackSettings").click();

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

@ -974,3 +974,16 @@ def is_windows(target, host):
return host.kernel == 'WINNT' and target.kernel == 'WINNT'
include('windows.configure', when=is_windows)
# Security Hardening
# ==============================================================
option('--enable-hardening', env='MOZ_SECURITY_HARDENING',
help='Enables security hardening compiler options')
@depends('--enable-hardening', c_compiler)
def security_hardening_cflags(value, c_compiler):
if value and c_compiler.type in ['gcc', 'clang']:
return '-fstack-protector-strong'
add_old_configure_assignment('HARDENING_CFLAGS', security_hardening_cflags)

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

@ -55,13 +55,12 @@ nsChromeRegistry::LogMessage(const char* aMsg, ...)
va_list args;
va_start(args, aMsg);
char* formatted = mozilla::Vsmprintf(aMsg, args);
mozilla::SmprintfPointer formatted = mozilla::Vsmprintf(aMsg, args);
va_end(args);
if (!formatted)
return;
console->LogStringMessage(NS_ConvertUTF8toUTF16(formatted).get());
mozilla::SmprintfFree(formatted);
console->LogStringMessage(NS_ConvertUTF8toUTF16(formatted.get()).get());
}
void
@ -80,7 +79,7 @@ nsChromeRegistry::LogMessageWithContext(nsIURI* aURL, uint32_t aLineNumber, uint
va_list args;
va_start(args, aMsg);
char* formatted = mozilla::Vsmprintf(aMsg, args);
mozilla::SmprintfPointer formatted = mozilla::Vsmprintf(aMsg, args);
va_end(args);
if (!formatted)
return;
@ -89,11 +88,10 @@ nsChromeRegistry::LogMessageWithContext(nsIURI* aURL, uint32_t aLineNumber, uint
if (aURL)
aURL->GetSpec(spec);
rv = error->Init(NS_ConvertUTF8toUTF16(formatted),
rv = error->Init(NS_ConvertUTF8toUTF16(formatted.get()),
NS_ConvertUTF8toUTF16(spec),
EmptyString(),
aLineNumber, 0, flags, "chrome registration");
mozilla::SmprintfFree(formatted);
if (NS_FAILED(rv))
return;

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

@ -740,7 +740,7 @@ nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext& cx, int lineno
if (strcmp(package, "global") == 0) {
// We should refresh the LocaleService, since the available
// locales changed.
LocaleService::GetInstance()->Refresh();
LocaleService::GetInstance()->OnAvailableLocalesChanged();
}
}

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

@ -105,15 +105,6 @@ function* testOptionsShortcut() {
function* testOptions() {
let tool = toolbox.getPanel("options");
panelWin = tool.panelWin;
// It's possible that the iframe for options hasn't fully loaded yet,
// and might be paint-suppressed, which means that clicking things
// might not work just yet. The "load" event is a good indication that
// we're ready to proceed.
if (tool.panelDoc.readyState != "complete") {
yield once(tool.panelWin, "load");
}
let prefNodes = tool.panelDoc.querySelectorAll(
"input[type=checkbox][data-pref]");

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

@ -5,6 +5,9 @@
"use strict";
// Toggling the toolbox three time can take more than 45s on slow test machine
requestLongerTimeout(2);
// Test toggling the toolbox quickly and see if there is any race breaking it.
const URL = "data:text/html;charset=utf-8,Toggling devtools quickly";

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

@ -4,7 +4,7 @@
"use strict";
// Test that the box model displays the right values for a pseduo-element.
// Test that the box model displays the right values for a pseudo-element.
const TEST_URI = `
<style type='text/css'>
@ -53,7 +53,7 @@ const res1 = [
},
{
selector: ".boxmodel-margin.boxmodel-left > span",
value: "auto"
value: 0
},
{
selector: ".boxmodel-margin.boxmodel-bottom > span",
@ -61,7 +61,7 @@ const res1 = [
},
{
selector: ".boxmodel-margin.boxmodel-right > span",
value: "auto"
value: 0
},
{
selector: ".boxmodel-padding.boxmodel-top > span",

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

@ -33,7 +33,7 @@ function* testTopLeft(inspector, view) {
let afterElement = children.nodes[children.nodes.length - 1];
yield selectNode(afterElement, inspector);
top = getComputedViewPropertyValue(view, "top");
is(top, "50%", "The computed view shows the correct top");
is(top, "96px", "The computed view shows the correct top");
left = getComputedViewPropertyValue(view, "left");
is(left, "50%", "The computed view shows the correct left");
is(left, "96px", "The computed view shows the correct left");
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше