зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
Коммит
8f4e4ca99b
|
@ -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/**
|
||||
|
|
|
@ -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,35 +1969,38 @@ 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);
|
||||
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);
|
||||
}
|
||||
TreeMutation mt(aContent->Parent());
|
||||
mt.BeforeRemoval(aContent);
|
||||
aContent->Parent()->RemoveChild(aContent);
|
||||
UncacheChildrenInSubtree(aContent);
|
||||
mt.Done();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
|
||||
{
|
||||
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -277,7 +277,7 @@ var SessionSaverInternal = {
|
|||
// Clear cookies and storage on clean shutdown.
|
||||
this._maybeClearCookiesAndStorage(state);
|
||||
|
||||
stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
|
||||
stopWatchFinish("COLLECT_DATA_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
|
||||
|
|
|
@ -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/"
|
||||
},
|
||||
{
|
||||
"title": "YouTube",
|
||||
"url": "https://www.youtube.com/"
|
||||
},
|
||||
{
|
||||
"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/"
|
||||
}
|
||||
]
|
||||
init: false,
|
||||
rows: []
|
||||
},
|
||||
Search: {
|
||||
currentEngine: {
|
||||
name: "",
|
||||
icon: ""
|
||||
},
|
||||
engines: []
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Handle some real actions here, once we have a TopSites feed working
|
||||
function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
||||
return prevState;
|
||||
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
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
||||
/***/ }
|
||||
/******/ ])
|
||||
});
|
||||
;
|
|
@ -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">
|
||||
|
|
|
@ -1,37 +1,8 @@
|
|||
<!-- 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" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
use:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
#light {
|
||||
fill: #999797;
|
||||
}
|
||||
#light-hovered {
|
||||
fill: #393f4c; /* --theme-body-color */
|
||||
}
|
||||
#light-selected {
|
||||
fill: #3b3b3b;
|
||||
}
|
||||
#dark {
|
||||
fill: #c6ccd0;
|
||||
}
|
||||
#dark-hovered {
|
||||
fill: #dde1e4;
|
||||
}
|
||||
#dark-selected {
|
||||
fill: #fcfcfc;
|
||||
}
|
||||
</style>
|
||||
<path id="base-path" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
|
||||
</defs>
|
||||
<use xlink:href="#base-path" id="light"/>
|
||||
<use xlink:href="#base-path" id="light-hovered"/>
|
||||
<use xlink:href="#base-path" id="light-selected"/>
|
||||
<use xlink:href="#base-path" id="dark"/>
|
||||
<use xlink:href="#base-path" id="dark-hovered"/>
|
||||
<use xlink:href="#base-path" id="dark-selected"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
|
||||
</svg>
|
||||
|
||||
|
|
До Ширина: | Высота: | Размер: 1.3 KiB После Ширина: | Высота: | Размер: 581 B |
|
@ -12,11 +12,7 @@
|
|||
--viewport-color: #999797;
|
||||
--viewport-hover-color: var(--theme-body-color);
|
||||
--viewport-active-color: #3b3b3b;
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg#light");
|
||||
--viewport-selection-arrow-hovered:
|
||||
url("./images/select-arrow.svg#light-hovered");
|
||||
--viewport-selection-arrow-selected:
|
||||
url("./images/select-arrow.svg#light-selected");
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg");
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
|
@ -26,11 +22,7 @@
|
|||
--viewport-color: #c6ccd0;
|
||||
--viewport-hover-color: #dde1e4;
|
||||
--viewport-active-color: #fcfcfc;
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg#dark");
|
||||
--viewport-selection-arrow-hovered:
|
||||
url("./images/select-arrow.svg#dark-hovered");
|
||||
--viewport-selection-arrow-selected:
|
||||
url("./images/select-arrow.svg#dark-selected");
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg");
|
||||
}
|
||||
|
||||
* {
|
||||
|
@ -91,11 +83,13 @@ select {
|
|||
-moz-appearance: none; appearance: none;
|
||||
background-color: var(--theme-toolbar-background);
|
||||
background-image: var(--viewport-selection-arrow);
|
||||
/* uncomment after bug 1350010 lands: context-properties: fill; */
|
||||
fill: currentColor;
|
||||
color: var(--viewport-color);
|
||||
background-position: 100% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 7px;
|
||||
border: none;
|
||||
color: var(--viewport-color);
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
text-align: center;
|
||||
|
@ -103,12 +97,10 @@ select {
|
|||
}
|
||||
|
||||
select.selected {
|
||||
background-image: var(--viewport-selection-arrow-selected);
|
||||
color: var(--viewport-active-color);
|
||||
}
|
||||
|
||||
select:not(:disabled):hover {
|
||||
background-image: var(--viewport-selection-arrow-hovered);
|
||||
color: var(--viewport-hover-color);
|
||||
}
|
||||
|
||||
|
@ -117,7 +109,6 @@ select:not(:disabled):hover {
|
|||
select is focused. It's unclear whether the visual effect that results here
|
||||
is intentional and desired. */
|
||||
select:focus {
|
||||
background-image: var(--viewport-selection-arrow-selected);
|
||||
color: var(--viewport-active-color);
|
||||
}
|
||||
|
||||
|
@ -203,12 +194,10 @@ select > option.divider {
|
|||
}
|
||||
|
||||
#global-dpr-selector:not(.disabled):hover > select {
|
||||
background-image: var(--viewport-selection-arrow-hovered);
|
||||
color: var(--viewport-hover-color);
|
||||
}
|
||||
|
||||
#global-dpr-selector:focus > select {
|
||||
background-image: var(--viewport-selection-arrow-selected);
|
||||
color: var(--viewport-active-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ Blob::Constructor(const GlobalObject& aGlobal,
|
|||
if (aData.WasPassed()) {
|
||||
nsAutoString type(aBag.mType);
|
||||
MakeValidBlobType(type);
|
||||
impl->InitializeBlob(aGlobal.Context(), aData.Value(), type,
|
||||
impl->InitializeBlob(aData.Value(), type,
|
||||
aBag.mEndings == EndingTypes::Native, aRv);
|
||||
} else {
|
||||
impl->InitializeBlob(aRv);
|
||||
|
|
|
@ -35,7 +35,7 @@ BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength)
|
|||
}
|
||||
|
||||
nsresult
|
||||
BlobSet::AppendString(const nsAString& aString, bool nativeEOL, JSContext* aCx)
|
||||
BlobSet::AppendString(const nsAString& aString, bool nativeEOL)
|
||||
{
|
||||
nsCString utf8Str = NS_ConvertUTF16toUTF8(aString);
|
||||
|
||||
|
@ -49,8 +49,9 @@ BlobSet::AppendString(const nsAString& aString, bool nativeEOL, JSContext* aCx)
|
|||
#endif
|
||||
}
|
||||
|
||||
return AppendVoidPtr((void*)utf8Str.Data(),
|
||||
utf8Str.Length());
|
||||
RefPtr<StringBlobImpl> blobImpl =
|
||||
StringBlobImpl::Create(utf8Str, EmptyString());
|
||||
return AppendBlobImpl(blobImpl);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -22,8 +22,7 @@ class BlobSet final
|
|||
public:
|
||||
nsresult AppendVoidPtr(const void* aData, uint32_t aLength);
|
||||
|
||||
nsresult AppendString(const nsAString& aString, bool nativeEOL,
|
||||
JSContext* aCx);
|
||||
nsresult AppendString(const nsAString& aString, bool nativeEOL);
|
||||
|
||||
nsresult AppendBlobImpl(BlobImpl* aBlobImpl);
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ File::Constructor(const GlobalObject& aGlobal,
|
|||
|
||||
nsAutoString type(aBag.mType);
|
||||
MakeValidBlobType(type);
|
||||
impl->InitializeBlob(aGlobal.Context(), aData, type, false, aRv);
|
||||
impl->InitializeBlob(aData, type, false, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -171,8 +171,7 @@ MultipartBlobImpl::InitializeBlob(ErrorResult& aRv)
|
|||
}
|
||||
|
||||
void
|
||||
MultipartBlobImpl::InitializeBlob(JSContext* aCx,
|
||||
const Sequence<Blob::BlobPart>& aData,
|
||||
MultipartBlobImpl::InitializeBlob(const Sequence<Blob::BlobPart>& aData,
|
||||
const nsAString& aContentType,
|
||||
bool aNativeEOL,
|
||||
ErrorResult& aRv)
|
||||
|
@ -189,7 +188,7 @@ MultipartBlobImpl::InitializeBlob(JSContext* aCx,
|
|||
}
|
||||
|
||||
else if (data.IsUSVString()) {
|
||||
aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL, aCx);
|
||||
aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ public:
|
|||
|
||||
void InitializeBlob(ErrorResult& aRv);
|
||||
|
||||
void InitializeBlob(JSContext* aCx,
|
||||
const Sequence<Blob::BlobPart>& aData,
|
||||
void InitializeBlob(const Sequence<Blob::BlobPart>& aData,
|
||||
const nsAString& aContentType,
|
||||
bool aNativeEOL,
|
||||
ErrorResult& aRv);
|
||||
|
|
|
@ -208,7 +208,7 @@ IPCBlobInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
|
|||
mCallbackEventTarget = aEventTarget;
|
||||
mState = ePending;
|
||||
|
||||
mActor->StreamNeeded(this);
|
||||
mActor->StreamNeeded(this, aEventTarget);
|
||||
return NS_OK;
|
||||
|
||||
// We are still waiting for the remote inputStream
|
||||
|
|
|
@ -145,14 +145,15 @@ IPCBlobInputStreamChild::ForgetStream(IPCBlobInputStream* aStream)
|
|||
}
|
||||
|
||||
void
|
||||
IPCBlobInputStreamChild::StreamNeeded(IPCBlobInputStream* aStream)
|
||||
IPCBlobInputStreamChild::StreamNeeded(IPCBlobInputStream* aStream,
|
||||
nsIEventTarget* aEventTarget)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mStreams.Contains(aStream));
|
||||
|
||||
PendingOperation* opt = mPendingOperations.AppendElement();
|
||||
opt->mStream = aStream;
|
||||
opt->mThread = NS_GetCurrentThread();
|
||||
opt->mEventTarget = aEventTarget ? aEventTarget : NS_GetCurrentThread();
|
||||
|
||||
if (mOwningThread == NS_GetCurrentThread()) {
|
||||
SendStreamNeeded();
|
||||
|
@ -172,7 +173,7 @@ IPCBlobInputStreamChild::RecvStreamReady(const OptionalIPCStream& aStream)
|
|||
|
||||
RefPtr<StreamReadyRunnable> runnable =
|
||||
new StreamReadyRunnable(mPendingOperations[0].mStream, stream);
|
||||
mPendingOperations[0].mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
mPendingOperations[0].mEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
|
||||
mPendingOperations.RemoveElementAt(0);
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ public:
|
|||
}
|
||||
|
||||
void
|
||||
StreamNeeded(IPCBlobInputStream* aStream);
|
||||
StreamNeeded(IPCBlobInputStream* aStream,
|
||||
nsIEventTarget* aEventTarget);
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvStreamReady(const OptionalIPCStream& aStream) override;
|
||||
|
@ -76,7 +77,7 @@ private:
|
|||
struct PendingOperation
|
||||
{
|
||||
RefPtr<IPCBlobInputStream> mStream;
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
};
|
||||
nsTArray<PendingOperation> mPendingOperations;
|
||||
|
||||
|
|
|
@ -55,3 +55,4 @@ if CONFIG['GNU_CXX']:
|
|||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
script_file.js
|
||||
|
||||
[test_ipcBlob_fileReaderSync.html]
|
|
@ -0,0 +1,14 @@
|
|||
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.importGlobalProperties(["File"]);
|
||||
|
||||
addMessageListener("file.open", function (e) {
|
||||
var testFile = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIDirectoryService)
|
||||
.QueryInterface(Ci.nsIProperties)
|
||||
.get("ProfD", Ci.nsIFile);
|
||||
testFile.append("prefs.js");
|
||||
|
||||
File.createFromNsIFile(testFile).then(function(file) {
|
||||
sendAsyncMessage("file.opened", { file });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test IPCBlob and FileReaderSync</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
function workerScript() {
|
||||
onmessage = function(event) {
|
||||
let readerMemoryBlob = new FileReaderSync();
|
||||
let status = readerMemoryBlob.readAsText(new Blob(['hello world'])) == 'hello world';
|
||||
|
||||
let readerIPCBlob = new FileReaderSync();
|
||||
postMessage({ blob: event.data, data: readerIPCBlob.readAsText(event.data), status });
|
||||
}
|
||||
}
|
||||
|
||||
let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
|
||||
let worker = new Worker(workerUrl);
|
||||
worker.onmessage = event => {
|
||||
let fr = new FileReader();
|
||||
fr.readAsText(event.data.blob);
|
||||
fr.onload = () => {
|
||||
is(event.data.data, fr.result, "The file has been read");
|
||||
ok(event.data.status, "FileReaderSync with memory blob still works");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
|
||||
let url = SimpleTest.getTestFileURL("script_file.js");
|
||||
let script = SpecialPowers.loadChromeScript(url);
|
||||
script.addMessageListener("file.opened", message => {
|
||||
worker.postMessage(message.file);
|
||||
});
|
||||
|
||||
script.sendAsyncMessage("file.open");
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -31,6 +31,7 @@
|
|||
#include "nsPrintfCString.h"
|
||||
#include "prsystem.h"
|
||||
#include "PluginQuirks.h"
|
||||
#include "gfxPlatform.h"
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
#include "CrossProcessProfilerController.h"
|
||||
#endif
|
||||
|
@ -2778,7 +2779,8 @@ PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance,
|
|||
// For all builds that use async rendering force use of the accelerated
|
||||
// direct path for flash objects that have wmode=window or no wmode
|
||||
// specified.
|
||||
if (supportsAsyncRender && supportsForceDirect) {
|
||||
if (supportsAsyncRender && supportsForceDirect &&
|
||||
gfxWindowsPlatform::GetPlatform()->SupportsPluginDirectDXGIDrawing()) {
|
||||
ForceDirect(names, values);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -22,16 +22,12 @@ interface Blob {
|
|||
|
||||
readonly attribute DOMString type;
|
||||
|
||||
// readonly attribute boolean isClosed; TODO bug 1048321
|
||||
|
||||
//slice Blob into byte-ranged chunks
|
||||
|
||||
[Throws]
|
||||
Blob slice([Clamp] optional long long start,
|
||||
[Clamp] optional long long end,
|
||||
optional DOMString contentType = "");
|
||||
|
||||
// void close(); TODO bug 1048325
|
||||
};
|
||||
|
||||
enum EndingTypes { "transparent", "native" };
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
#include "nsISupportsImpl.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
|
||||
#include "RuntimeService.h"
|
||||
|
||||
|
@ -75,7 +78,7 @@ FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
|
|||
}
|
||||
|
||||
uint32_t numRead;
|
||||
aRv = stream->Read(bufferData.get(), blobSize, &numRead);
|
||||
aRv = Read(stream, bufferData.get(), blobSize, &numRead);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
@ -107,7 +110,7 @@ FileReaderSync::ReadAsBinaryString(Blob& aBlob,
|
|||
uint32_t numRead;
|
||||
do {
|
||||
char readBuf[4096];
|
||||
aRv = stream->Read(readBuf, sizeof(readBuf), &numRead);
|
||||
aRv = Read(stream, readBuf, sizeof(readBuf), &numRead);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
@ -142,7 +145,7 @@ FileReaderSync::ReadAsText(Blob& aBlob,
|
|||
}
|
||||
|
||||
uint32_t numRead = 0;
|
||||
aRv = stream->Read(sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
|
||||
aRv = Read(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
@ -288,3 +291,122 @@ FileReaderSync::ConvertStream(nsIInputStream *aStream,
|
|||
return rv;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// This runnable is used to terminate the sync event loop.
|
||||
class ReadReadyRunnable final : public WorkerSyncRunnable
|
||||
{
|
||||
public:
|
||||
ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
|
||||
nsIEventTarget* aSyncLoopTarget)
|
||||
: WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
|
||||
{}
|
||||
|
||||
bool
|
||||
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
|
||||
{
|
||||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||||
MOZ_ASSERT(mSyncLoopTarget);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> syncLoopTarget;
|
||||
mSyncLoopTarget.swap(syncLoopTarget);
|
||||
|
||||
aWorkerPrivate->StopSyncLoop(syncLoopTarget, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
~ReadReadyRunnable()
|
||||
{}
|
||||
};
|
||||
|
||||
// This class implements nsIInputStreamCallback and it will be called when the
|
||||
// stream is ready to be read.
|
||||
class ReadCallback final : public nsIInputStreamCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
|
||||
: mWorkerPrivate(aWorkerPrivate)
|
||||
, mEventTarget(aEventTarget)
|
||||
{}
|
||||
|
||||
NS_IMETHOD
|
||||
OnInputStreamReady(nsIAsyncInputStream* aStream) override
|
||||
{
|
||||
// I/O Thread. Now we need to block the sync event loop.
|
||||
RefPtr<ReadReadyRunnable> runnable =
|
||||
new ReadReadyRunnable(mWorkerPrivate, mEventTarget);
|
||||
return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
private:
|
||||
~ReadCallback()
|
||||
{}
|
||||
|
||||
// The worker is kept alive because of the sync event loop.
|
||||
WorkerPrivate* mWorkerPrivate;
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
};
|
||||
|
||||
NS_IMPL_ADDREF(ReadCallback);
|
||||
NS_IMPL_RELEASE(ReadCallback);
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN(ReadCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
} // anonymous
|
||||
|
||||
nsresult
|
||||
FileReaderSync::Read(nsIInputStream* aStream, char* aBuffer, uint32_t aBufferSize,
|
||||
uint32_t* aRead)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(aBuffer);
|
||||
MOZ_ASSERT(aRead);
|
||||
|
||||
// Let's try to read, directly.
|
||||
nsresult rv = aStream->Read(aBuffer, aBufferSize, aRead);
|
||||
if (NS_SUCCEEDED(rv) || rv != NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We need to proceed async.
|
||||
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
|
||||
if (!asyncStream) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
|
||||
AutoSyncLoopHolder syncLoop(workerPrivate, Closing);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
|
||||
if (!syncLoopTarget) {
|
||||
// SyncLoop creation can fail if the worker is shutting down.
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
RefPtr<ReadCallback> callback =
|
||||
new ReadCallback(workerPrivate, syncLoopTarget);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target =
|
||||
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
MOZ_ASSERT(target);
|
||||
|
||||
rv = asyncStream->AsyncWait(callback, 0, aBufferSize, target);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!syncLoop.Run()) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
// Now, we can try to read again.
|
||||
return Read(aStream, aBuffer, aBufferSize, aRead);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ private:
|
|||
nsresult ConvertStream(nsIInputStream *aStream, const char *aCharset,
|
||||
nsAString &aResult);
|
||||
|
||||
nsresult Read(nsIInputStream* aStream, char* aBuffer, uint32_t aBufferSize,
|
||||
uint32_t* aRead);
|
||||
|
||||
public:
|
||||
static already_AddRefed<FileReaderSync>
|
||||
Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
|
||||
|
|
|
@ -278,6 +278,10 @@ AndroidDynamicToolbarAnimator::ToolbarAnimatorMessageFromUI(int32_t aMessage)
|
|||
case REQUEST_HIDE_TOOLBAR_ANIMATED:
|
||||
NotifyControllerPendingAnimation(MOVE_TOOLBAR_UP, eAnimate);
|
||||
break;
|
||||
case TOOLBAR_SNAPSHOT_FAILED:
|
||||
mToolbarState = eToolbarVisible;
|
||||
NotifyControllerSnapshotFailed();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -893,5 +897,18 @@ AndroidDynamicToolbarAnimator::GetFixedLayerMarginsBottom()
|
|||
return mCompositorToolbarHeight - (mCompositorSurfaceHeight - mCompositorCompositionSize.height);
|
||||
}
|
||||
|
||||
void
|
||||
AndroidDynamicToolbarAnimator::NotifyControllerSnapshotFailed()
|
||||
{
|
||||
if (!APZThreadUtils::IsControllerThread()) {
|
||||
APZThreadUtils::RunOnControllerThread(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::NotifyControllerSnapshotFailed));
|
||||
return;
|
||||
}
|
||||
|
||||
mControllerToolbarHeight = 0;
|
||||
mControllerState = eNothingPending;
|
||||
UpdateCompositorToolbarHeight(mControllerToolbarHeight);
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -149,6 +149,7 @@ protected:
|
|||
void ShowToolbarIfNotVisible(StaticToolbarState aCurrentToolbarState);
|
||||
void TranslateTouchEvent(MultiTouchInput& aTouchEvent);
|
||||
ScreenIntCoord GetFixedLayerMarginsBottom();
|
||||
void NotifyControllerSnapshotFailed();
|
||||
|
||||
// Read only Compositor and Controller threads after Initialize()
|
||||
uint64_t mRootLayerTreeId;
|
||||
|
|
|
@ -130,6 +130,11 @@ UiCompositorControllerChild::ToolbarAnimatorMessageFromUI(const int32_t& aMessag
|
|||
return false;
|
||||
}
|
||||
|
||||
if (aMessage == IS_COMPOSITOR_CONTROLLER_OPEN) {
|
||||
RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
return SendToolbarAnimatorMessageFromUI(aMessage);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ enum UiCompositorControllerMessageTypes {
|
|||
REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8, // Sent to the compositor when the snapshot should be hidden immediately
|
||||
REQUEST_HIDE_TOOLBAR_ANIMATED = 9, // Sent to the compositor when the snapshot should be hidden with an animation
|
||||
LAYERS_UPDATED = 10, // Sent from the compositor when any layer has been updated
|
||||
COMPOSITOR_CONTROLLER_OPEN = 20 // Compositor controller IPC is open
|
||||
TOOLBAR_SNAPSHOT_FAILED = 11, // Sent to compositor when the toolbar snapshot fails.
|
||||
COMPOSITOR_CONTROLLER_OPEN = 20, // Compositor controller IPC is open
|
||||
IS_COMPOSITOR_CONTROLLER_OPEN = 21 // Special message sent from controller to query if the compositor controller is open
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
|
|
@ -2029,56 +2029,18 @@ MessageChannel::DispatchInterruptMessage(Message&& aMsg, size_t stackDepth)
|
|||
|
||||
IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
|
||||
|
||||
// Race detection: see the long comment near mRemoteStackDepthGuess in
|
||||
// MessageChannel.h. "Remote" stack depth means our side, and "local" means
|
||||
// the other side.
|
||||
if (aMsg.interrupt_remote_stack_depth_guess() != RemoteViewOfStackDepth(stackDepth)) {
|
||||
// Interrupt in-calls have raced. The winner, if there is one, gets to defer
|
||||
// processing of the other side's in-call.
|
||||
bool defer;
|
||||
const char* winner;
|
||||
const MessageInfo parentMsgInfo =
|
||||
(mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
|
||||
const MessageInfo childMsgInfo =
|
||||
(mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
|
||||
switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo))
|
||||
{
|
||||
case RIPChildWins:
|
||||
winner = "child";
|
||||
defer = (mSide == ChildSide);
|
||||
break;
|
||||
case RIPParentWins:
|
||||
winner = "parent";
|
||||
defer = (mSide != ChildSide);
|
||||
break;
|
||||
case RIPError:
|
||||
MOZ_CRASH("NYI: 'Error' Interrupt race policy");
|
||||
return;
|
||||
default:
|
||||
MOZ_CRASH("not reached");
|
||||
return;
|
||||
}
|
||||
|
||||
if (LoggingEnabled()) {
|
||||
printf_stderr(" (%s: %s won, so we're%sdeferring)\n",
|
||||
(mSide == ChildSide) ? "child" : "parent",
|
||||
winner,
|
||||
defer ? " " : " not ");
|
||||
}
|
||||
|
||||
if (defer) {
|
||||
// We now know the other side's stack has one more frame
|
||||
// than we thought.
|
||||
++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred()
|
||||
mDeferred.push(Move(aMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
// We "lost" and need to process the other side's in-call. Don't need
|
||||
// to fix up the mRemoteStackDepthGuess here, because we're just about
|
||||
// to increment it in DispatchCall(), which will make it correct again.
|
||||
if (ShouldDeferInterruptMessage(aMsg, stackDepth)) {
|
||||
// We now know the other side's stack has one more frame
|
||||
// than we thought.
|
||||
++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred()
|
||||
mDeferred.push(Move(aMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
// If we "lost" a race and need to process the other side's in-call, we
|
||||
// don't need to fix up the mRemoteStackDepthGuess here, because we're just
|
||||
// about to increment it, which will make it correct again.
|
||||
|
||||
#ifdef OS_WIN
|
||||
SyncStackFrame frame(this, true);
|
||||
#endif
|
||||
|
@ -2103,6 +2065,54 @@ MessageChannel::DispatchInterruptMessage(Message&& aMsg, size_t stackDepth)
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MessageChannel::ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth)
|
||||
{
|
||||
AssertWorkerThread();
|
||||
|
||||
// We may or may not own the lock in this function, so don't access any
|
||||
// channel state.
|
||||
|
||||
IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
|
||||
|
||||
// Race detection: see the long comment near mRemoteStackDepthGuess in
|
||||
// MessageChannel.h. "Remote" stack depth means our side, and "local" means
|
||||
// the other side.
|
||||
if (aMsg.interrupt_remote_stack_depth_guess() == RemoteViewOfStackDepth(aStackDepth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Interrupt in-calls have raced. The winner, if there is one, gets to defer
|
||||
// processing of the other side's in-call.
|
||||
bool defer;
|
||||
const char* winner;
|
||||
const MessageInfo parentMsgInfo =
|
||||
(mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
|
||||
const MessageInfo childMsgInfo =
|
||||
(mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
|
||||
switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo))
|
||||
{
|
||||
case RIPChildWins:
|
||||
winner = "child";
|
||||
defer = (mSide == ChildSide);
|
||||
break;
|
||||
case RIPParentWins:
|
||||
winner = "parent";
|
||||
defer = (mSide != ChildSide);
|
||||
break;
|
||||
case RIPError:
|
||||
MOZ_CRASH("NYI: 'Error' Interrupt race policy");
|
||||
default:
|
||||
MOZ_CRASH("not reached");
|
||||
}
|
||||
|
||||
IPC_LOG("race in %s: %s won",
|
||||
(mSide == ChildSide) ? "child" : "parent",
|
||||
winner);
|
||||
|
||||
return defer;
|
||||
}
|
||||
|
||||
void
|
||||
MessageChannel::MaybeUndeferIncall()
|
||||
{
|
||||
|
@ -2114,12 +2124,18 @@ MessageChannel::MaybeUndeferIncall()
|
|||
|
||||
size_t stackDepth = InterruptStackDepth();
|
||||
|
||||
Message& deferred = mDeferred.top();
|
||||
|
||||
// the other side can only *under*-estimate our actual stack depth
|
||||
IPC_ASSERT(mDeferred.top().interrupt_remote_stack_depth_guess() <= stackDepth,
|
||||
IPC_ASSERT(deferred.interrupt_remote_stack_depth_guess() <= stackDepth,
|
||||
"fatal logic error");
|
||||
|
||||
if (ShouldDeferInterruptMessage(deferred, stackDepth)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// maybe time to process this message
|
||||
Message call(Move(mDeferred.top()));
|
||||
Message call(Move(deferred));
|
||||
mDeferred.pop();
|
||||
|
||||
// fix up fudge factor we added to account for race
|
||||
|
|
|
@ -476,6 +476,7 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
|
|||
|
||||
bool WasTransactionCanceled(int transaction);
|
||||
bool ShouldDeferMessage(const Message& aMsg);
|
||||
bool ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth);
|
||||
void OnMessageReceivedFromLink(Message&& aMsg);
|
||||
void OnChannelErrorFromLink();
|
||||
|
||||
|
|
|
@ -88,8 +88,4 @@ private:
|
|||
;
|
||||
};
|
||||
|
||||
|
||||
nsresult
|
||||
NS_NewPagePrintTimer(nsPagePrintTimer **aResult);
|
||||
|
||||
#endif /* nsPagePrintTimer_h___ */
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// Keep others in (case-insensitive) order:
|
||||
#include "gfxUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
|
@ -30,7 +31,7 @@ SVGImageContext::MaybeStoreContextPaint(Maybe<SVGImageContext>& aContext,
|
|||
}
|
||||
|
||||
if (!sEnabledForContent &&
|
||||
!aFromFrame->PresContext()->IsChrome()) {
|
||||
!nsContentUtils::IsChromeDoc(aFromFrame->PresContext()->Document())) {
|
||||
// Context paint is pref'ed off for content and this is a content doc.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,15 @@ namespace mozilla {
|
|||
|
||||
namespace detail {
|
||||
|
||||
#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN) || defined(XP_MACOSX)
|
||||
#ifdef XP_MACOSX
|
||||
# if defined(__has_feature)
|
||||
# if __has_feature(cxx_thread_local)
|
||||
# define MACOSX_HAS_THREAD_LOCAL
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)
|
||||
#define MOZ_HAS_THREAD_LOCAL
|
||||
#endif
|
||||
|
||||
|
@ -171,7 +179,7 @@ ThreadLocal<T>::set(const T aValue)
|
|||
}
|
||||
|
||||
#ifdef MOZ_HAS_THREAD_LOCAL
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
#if defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)
|
||||
#define MOZ_THREAD_LOCAL(TYPE) thread_local mozilla::detail::ThreadLocal<TYPE>
|
||||
#else
|
||||
#define MOZ_THREAD_LOCAL(TYPE) __thread mozilla::detail::ThreadLocal<TYPE>
|
||||
|
|
|
@ -258,6 +258,12 @@ public class BrowserApp extends GeckoApp
|
|||
private static final int ADDON_MENU_OFFSET = 1000;
|
||||
public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
|
||||
|
||||
// When the static action bar is shown, only the real toolbar chrome should be
|
||||
// shown when the toolbar is visible. Causing the toolbar animator to also
|
||||
// show the snapshot causes the content to shift under the users finger.
|
||||
// See: Bug 1358554
|
||||
private boolean mShowingToolbarChromeForActionBar;
|
||||
|
||||
private static class MenuItemInfo {
|
||||
public int id;
|
||||
public String label;
|
||||
|
@ -386,7 +392,10 @@ public class BrowserApp extends GeckoApp
|
|||
updateHomePagerForTab(tab);
|
||||
}
|
||||
|
||||
mDynamicToolbar.persistTemporaryVisibility();
|
||||
if (mShowingToolbarChromeForActionBar) {
|
||||
mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
|
||||
mShowingToolbarChromeForActionBar = false;
|
||||
}
|
||||
break;
|
||||
case START:
|
||||
if (Tabs.getInstance().isSelectedTab(tab)) {
|
||||
|
@ -4036,6 +4045,7 @@ public class BrowserApp extends GeckoApp
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
/* Implementing ActionModeCompat.Presenter */
|
||||
@Override
|
||||
public void startActionMode(final ActionModeCompat.Callback callback) {
|
||||
|
@ -4044,9 +4054,11 @@ public class BrowserApp extends GeckoApp
|
|||
mActionBarFlipper.showNext();
|
||||
DynamicToolbarAnimator toolbar = mLayerView.getDynamicToolbarAnimator();
|
||||
|
||||
// If the toolbar is dynamic and not currently showing, just slide it in
|
||||
// If the toolbar is dynamic and not currently showing, just show the real toolbar
|
||||
// and keep the animated snapshot hidden
|
||||
if (mDynamicToolbar.isEnabled() && toolbar.getCurrentToolbarHeight() == 0) {
|
||||
mDynamicToolbar.setTemporarilyVisible(true, VisibilityTransition.ANIMATE);
|
||||
toggleToolbarChrome(true);
|
||||
mShowingToolbarChromeForActionBar = true;
|
||||
}
|
||||
mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
|
||||
|
||||
|
@ -4075,9 +4087,12 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
mActionBarFlipper.showPrevious();
|
||||
|
||||
// Only slide the urlbar out if it was hidden when the action mode started
|
||||
// Don't animate hiding it so that there's no flash as we switch back to url mode
|
||||
mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
|
||||
// Hide the real toolbar chrome if it was hidden before the action bar
|
||||
// was shown.
|
||||
if (mShowingToolbarChromeForActionBar) {
|
||||
toggleToolbarChrome(false);
|
||||
mShowingToolbarChromeForActionBar = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface TabStripInterface {
|
||||
|
|
|
@ -149,36 +149,6 @@ public class DynamicToolbar {
|
|||
}
|
||||
}
|
||||
|
||||
public void setTemporarilyVisible(boolean visible, VisibilityTransition transition) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
if (layerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible == temporarilyVisible) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
temporarilyVisible = visible;
|
||||
final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
|
||||
if (visible) {
|
||||
layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
|
||||
} else {
|
||||
layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
|
||||
}
|
||||
}
|
||||
|
||||
public void persistTemporaryVisibility() {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
if (temporarilyVisible) {
|
||||
temporarilyVisible = false;
|
||||
setVisible(true, VisibilityTransition.IMMEDIATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPinned(boolean pinned, PinReason reason) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
if (layerView == null) {
|
||||
|
|
|
@ -39,6 +39,8 @@ import org.mozilla.gecko.Tab;
|
|||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
|
||||
import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuInflater;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
|
@ -216,6 +218,14 @@ public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedLi
|
|||
}
|
||||
}
|
||||
super.onResume();
|
||||
|
||||
mLayerView.getDynamicToolbarAnimator().setPinned(true, PinReason.CUSTOM_TAB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mLayerView.getDynamicToolbarAnimator().setPinned(false, PinReason.CUSTOM_TAB);
|
||||
}
|
||||
|
||||
// Usually should use onCreateOptionsMenu() to initialize menu items. But GeckoApp overwrite
|
||||
|
|
|
@ -27,7 +27,8 @@ public class DynamicToolbarAnimator {
|
|||
ACTION_MODE(2),
|
||||
FULL_SCREEN(3),
|
||||
CARET_DRAG(4),
|
||||
PAGE_LOADING(5);
|
||||
PAGE_LOADING(5),
|
||||
CUSTOM_TAB(6);
|
||||
|
||||
public final int mValue;
|
||||
PinReason(final int value) {
|
||||
|
@ -154,9 +155,19 @@ public class DynamicToolbarAnimator {
|
|||
private void dumpStateToCompositor() {
|
||||
if ((mCompositor != null) && mCompositorControllerOpen) {
|
||||
mCompositor.setMaxToolbarHeight(mMaxToolbarHeight);
|
||||
if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
|
||||
mCompositor.sendToolbarAnimatorMessage(LayerView.REQUEST_SHOW_TOOLBAR_IMMEDIATELY);
|
||||
} else {
|
||||
mCompositor.sendToolbarAnimatorMessage(LayerView.REQUEST_HIDE_TOOLBAR_IMMEDIATELY);
|
||||
}
|
||||
for (PinReason reason : pinFlags) {
|
||||
mCompositor.setPinned(true, reason.mValue);
|
||||
}
|
||||
} else if ((mCompositor != null) && !mCompositorControllerOpen) {
|
||||
// Ask the UiCompositorControllerChild if it is open since the open message can
|
||||
// sometimes be sent to a different instance of the LayerView such as when
|
||||
// Fennec is being used in custom tabs.
|
||||
mCompositor.sendToolbarAnimatorMessage(LayerView.IS_COMPOSITOR_CONTROLLER_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,10 @@ public class LayerView extends FrameLayout {
|
|||
/* package */ final static int REQUEST_SHOW_TOOLBAR_ANIMATED = 7; // Sent to compositor requesting toolbar be shown animated
|
||||
/* package */ final static int REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8; // Sent to compositor requesting toolbar be hidden immediately
|
||||
/* package */ final static int REQUEST_HIDE_TOOLBAR_ANIMATED = 9; // Sent to compositor requesting toolbar be hidden animated
|
||||
/* package */ final static int LAYERS_UPDATED = 10; // Sent from compositor when a layer has been updated
|
||||
/* package */ final static int LAYERS_UPDATED = 10; // Sent from compositor when a layer has been updated
|
||||
/* package */ final static int TOOLBAR_SNAPSHOT_FAILED = 11; // Sent to compositor when the toolbar snapshot fails.
|
||||
/* package */ final static int COMPOSITOR_CONTROLLER_OPEN = 20; // Special message sent from UiCompositorControllerChild once it is open
|
||||
/* package */ final static int IS_COMPOSITOR_CONTROLLER_OPEN = 21; // Special message sent from controller to query if the compositor controller is open
|
||||
|
||||
private void postCompositorMessage(final int message) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
|
@ -206,6 +208,7 @@ public class LayerView extends FrameLayout {
|
|||
// Send updated toolbar image to compositor.
|
||||
Bitmap bm = mToolbarAnimator.getBitmapOfToolbarChrome();
|
||||
if (bm == null) {
|
||||
postCompositorMessage(TOOLBAR_SNAPSHOT_FAILED);
|
||||
break;
|
||||
}
|
||||
final int width = bm.getWidth();
|
||||
|
|
|
@ -17,6 +17,7 @@ support-files =
|
|||
support-files =
|
||||
form_cross_origin_insecure_action.html
|
||||
[browser_capture_doorhanger.js]
|
||||
skip-if = os == "linux" && debug # Bug 1334336
|
||||
support-files =
|
||||
subtst_notifications_1.html
|
||||
subtst_notifications_2.html
|
||||
|
|
|
@ -5623,14 +5623,6 @@
|
|||
"n_buckets": 10,
|
||||
"description": "Session restore: Time to collect all window data (ms)"
|
||||
},
|
||||
"FX_SESSION_RESTORE_COLLECT_COOKIES_MS": {
|
||||
"alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 10,
|
||||
"description": "Session restore: Time to collect cookies (ms)"
|
||||
},
|
||||
"FX_SESSION_RESTORE_COLLECT_DATA_MS": {
|
||||
"alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "default",
|
||||
|
@ -5639,14 +5631,6 @@
|
|||
"n_buckets": 10,
|
||||
"description": "Session restore: Time to collect all window and tab data (ms)"
|
||||
},
|
||||
"FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS": {
|
||||
"alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 10,
|
||||
"description": "Session restore: Duration of the longest uninterruptible operation while collecting all window and tab data (ms)"
|
||||
},
|
||||
"FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS": {
|
||||
"alert_emails": ["session-restore-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "default",
|
||||
|
|
|
@ -940,8 +940,6 @@
|
|||
"FX_SESSION_RESTORE_ALL_FILES_CORRUPT",
|
||||
"FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS",
|
||||
"FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS",
|
||||
"FX_SESSION_RESTORE_COLLECT_COOKIES_MS",
|
||||
"FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS",
|
||||
"FX_SESSION_RESTORE_COLLECT_DATA_MS",
|
||||
"FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS",
|
||||
"FX_SESSION_RESTORE_CORRUPT_FILE",
|
||||
|
|
|
@ -90,8 +90,8 @@ private:
|
|||
// This is only used for the main thread.
|
||||
mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
|
||||
|
||||
// When sampling, this holds the generation number and offset in
|
||||
// ProfilerState::mBuffer of the most recent sample for this thread.
|
||||
// When sampling, this holds the generation number and offset in PS::mBuffer
|
||||
// of the most recent sample for this thread.
|
||||
ProfileBuffer::LastSample mLastSample;
|
||||
};
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ ThreadEntry(void* aArg)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
|
@ -343,7 +343,7 @@ SamplerThread::~SamplerThread()
|
|||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PS::LockRef aLock)
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
|
@ -356,7 +356,7 @@ SamplerThread::Stop(PS::LockRef aLock)
|
|||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
{
|
||||
// Only one sampler thread can be sampling at once. So we expect to have
|
||||
|
@ -466,7 +466,7 @@ paf_prepare()
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
gPS->SetWasPaused(lock, gPS->IsPaused(lock));
|
||||
gPS->SetIsPaused(lock, true);
|
||||
|
@ -480,14 +480,14 @@ paf_parent()
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
gPS->SetIsPaused(lock, gPS->WasPaused(lock));
|
||||
gPS->SetWasPaused(lock, false);
|
||||
}
|
||||
|
||||
static void
|
||||
PlatformInit(PS::LockRef aLock)
|
||||
PlatformInit(PSLockRef aLock)
|
||||
{
|
||||
// Set up the fork handlers.
|
||||
pthread_atfork(paf_prepare, paf_parent, nullptr);
|
||||
|
@ -496,7 +496,7 @@ PlatformInit(PS::LockRef aLock)
|
|||
#else
|
||||
|
||||
static void
|
||||
PlatformInit(PS::LockRef aLock)
|
||||
PlatformInit(PSLockRef aLock)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ ThreadEntry(void* aArg)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
|
@ -121,13 +121,13 @@ SamplerThread::~SamplerThread()
|
|||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PS::LockRef aLock)
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
{
|
||||
thread_act_t samplee_thread = aSample.mPlatformData->ProfiledThread();
|
||||
|
@ -204,7 +204,7 @@ SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void
|
||||
PlatformInit(PS::LockRef aLock)
|
||||
PlatformInit(PSLockRef aLock)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ ThreadEntry(void* aArg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
SamplerThread::SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
|
||||
SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds)
|
||||
: mActivityGeneration(aActivityGeneration)
|
||||
, mIntervalMicroseconds(
|
||||
|
@ -139,7 +139,7 @@ SamplerThread::~SamplerThread()
|
|||
}
|
||||
|
||||
void
|
||||
SamplerThread::Stop(PS::LockRef aLock)
|
||||
SamplerThread::Stop(PSLockRef aLock)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
|
@ -157,7 +157,7 @@ SamplerThread::Stop(PS::LockRef aLock)
|
|||
}
|
||||
|
||||
void
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
|
||||
SamplerThread::SuspendAndSampleAndResumeThread(PSLockRef aLock,
|
||||
TickSample& aSample)
|
||||
{
|
||||
HANDLE profiled_thread = aSample.mPlatformData->ProfiledThread();
|
||||
|
@ -229,7 +229,7 @@ SamplerThread::SuspendAndSampleAndResumeThread(PS::LockRef aLock,
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void
|
||||
PlatformInit(PS::LockRef aLock)
|
||||
PlatformInit(PSLockRef aLock)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -110,34 +110,33 @@ class SamplerThread;
|
|||
// Per-thread state.
|
||||
MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
|
||||
|
||||
class PSMutex : public mozilla::StaticMutex {};
|
||||
|
||||
typedef mozilla::BaseAutoLock<PSMutex> PSAutoLock;
|
||||
|
||||
// Only functions that take a PSLockRef arg can modify this class's fields.
|
||||
typedef const PSAutoLock& PSLockRef;
|
||||
|
||||
// This class contains most of the profiler's global state. gPS is the single
|
||||
// instance. Most profile operations can't do anything useful when gPS is not
|
||||
// instantiated, so we release-assert its non-nullness in all such operations.
|
||||
//
|
||||
// Accesses to gPS are guarded by gPSMutex. Every getter and setter takes a
|
||||
// PS::AutoLock reference as an argument as proof that the gPSMutex is
|
||||
// currently locked. This makes it clear when gPSMutex is locked and helps
|
||||
// avoid accidental unlocked accesses to global state. There are ways to
|
||||
// circumvent this mechanism, but please don't do so without *very* good reason
|
||||
// and a detailed explanation.
|
||||
// PSAutoLock reference as an argument as proof that the gPSMutex is currently
|
||||
// locked. This makes it clear when gPSMutex is locked and helps avoid
|
||||
// accidental unlocked accesses to global state. There are ways to circumvent
|
||||
// this mechanism, but please don't do so without *very* good reason and a
|
||||
// detailed explanation.
|
||||
//
|
||||
// Other from the lock protection, this class is essentially a thin wrapper and
|
||||
// contains very little "smarts" itself.
|
||||
//
|
||||
class ProfilerState
|
||||
class PS
|
||||
{
|
||||
public:
|
||||
// Shorter names for local use.
|
||||
class Mutex : public mozilla::StaticMutex {};
|
||||
|
||||
typedef mozilla::BaseAutoLock<Mutex> AutoLock;
|
||||
|
||||
// Only functions that take a LockRef arg can modify this class's fields.
|
||||
typedef const AutoLock& LockRef;
|
||||
|
||||
typedef std::vector<ThreadInfo*> ThreadVector;
|
||||
|
||||
ProfilerState()
|
||||
PS()
|
||||
: mEntries(0)
|
||||
, mInterval(0)
|
||||
, mFeatureDisplayListDump(false)
|
||||
|
@ -155,7 +154,7 @@ public:
|
|||
, mFeatureThreads(false)
|
||||
, mBuffer(nullptr)
|
||||
, mIsPaused(false)
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
#if defined(GP_OS_linux)
|
||||
, mWasPaused(false)
|
||||
#endif
|
||||
, mSamplerThread(nullptr)
|
||||
|
@ -168,18 +167,18 @@ public:
|
|||
{}
|
||||
|
||||
#define GET_AND_SET(type_, name_) \
|
||||
type_ name_(LockRef) const { return m##name_; } \
|
||||
void Set##name_(LockRef, type_ a##name_) { m##name_ = a##name_; }
|
||||
type_ name_(PSLockRef) const { return m##name_; } \
|
||||
void Set##name_(PSLockRef, type_ a##name_) { m##name_ = a##name_; }
|
||||
|
||||
GET_AND_SET(TimeStamp, StartTime)
|
||||
GET_AND_SET(TimeStamp, ProcessStartTime)
|
||||
|
||||
GET_AND_SET(int, Entries)
|
||||
|
||||
GET_AND_SET(double, Interval)
|
||||
|
||||
Vector<std::string>& Features(LockRef) { return mFeatures; }
|
||||
Vector<std::string>& Features(PSLockRef) { return mFeatures; }
|
||||
|
||||
Vector<std::string>& ThreadNameFilters(LockRef) { return mThreadNameFilters; }
|
||||
Vector<std::string>& Filters(PSLockRef) { return mFilters; }
|
||||
|
||||
GET_AND_SET(bool, FeatureDisplayListDump)
|
||||
GET_AND_SET(bool, FeatureGPU)
|
||||
|
@ -197,13 +196,13 @@ public:
|
|||
|
||||
GET_AND_SET(ProfileBuffer*, Buffer)
|
||||
|
||||
ThreadVector& LiveThreads(LockRef) { return mLiveThreads; }
|
||||
ThreadVector& DeadThreads(LockRef) { return mDeadThreads; }
|
||||
ThreadVector& LiveThreads(PSLockRef) { return mLiveThreads; }
|
||||
ThreadVector& DeadThreads(PSLockRef) { return mDeadThreads; }
|
||||
|
||||
static bool IsActive(LockRef) { return sActivityGeneration > 0; }
|
||||
static uint32_t ActivityGeneration(LockRef) { return sActivityGeneration; }
|
||||
static void SetInactive(LockRef) { sActivityGeneration = 0; }
|
||||
static void SetActive(LockRef)
|
||||
static bool IsActive(PSLockRef) { return sActivityGeneration > 0; }
|
||||
static uint32_t ActivityGeneration(PSLockRef) { return sActivityGeneration; }
|
||||
static void SetInactive(PSLockRef) { sActivityGeneration = 0; }
|
||||
static void SetActive(PSLockRef)
|
||||
{
|
||||
sActivityGeneration = sNextActivityGeneration;
|
||||
// On overflow, reset to 1 instead of 0, because 0 means inactive.
|
||||
|
@ -214,7 +213,7 @@ public:
|
|||
|
||||
GET_AND_SET(bool, IsPaused)
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
#if defined(GP_OS_linux)
|
||||
GET_AND_SET(bool, WasPaused)
|
||||
#endif
|
||||
|
||||
|
@ -232,8 +231,8 @@ public:
|
|||
#undef GET_AND_SET
|
||||
|
||||
private:
|
||||
// When profiler_init() or profiler_start() was most recently called.
|
||||
mozilla::TimeStamp mStartTime;
|
||||
// The time that the process started.
|
||||
mozilla::TimeStamp mProcessStartTime;
|
||||
|
||||
// The number of entries in mBuffer. Zeroed when the profiler is inactive.
|
||||
int mEntries;
|
||||
|
@ -248,7 +247,7 @@ private:
|
|||
|
||||
// Substrings of names of threads we want to profile. Cleared when the
|
||||
// profiler is inactive
|
||||
Vector<std::string> mThreadNameFilters;
|
||||
Vector<std::string> mFilters;
|
||||
|
||||
// Configuration flags derived from mFeatures. Cleared when the profiler is
|
||||
// inactive.
|
||||
|
@ -306,7 +305,7 @@ private:
|
|||
// Is the profiler paused? False when the profiler is inactive.
|
||||
bool mIsPaused;
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android)
|
||||
#if defined(GP_OS_linux)
|
||||
// Used to record whether the profiler was paused just before forking. False
|
||||
// at all times except just before/after forking.
|
||||
bool mWasPaused;
|
||||
|
@ -330,17 +329,17 @@ private:
|
|||
int mLatestRecordedFrameNumber;
|
||||
};
|
||||
|
||||
// A shorter name for use within this compilation unit.
|
||||
typedef ProfilerState PS;
|
||||
|
||||
uint32_t PS::sActivityGeneration = 0;
|
||||
uint32_t PS::sNextActivityGeneration = 1;
|
||||
|
||||
// The profiler state. Set by profiler_init(), cleared by profiler_shutdown().
|
||||
PS* gPS = nullptr;
|
||||
// The core profiler state. Null at process startup, it is set to a non-null
|
||||
// value in profiler_init() and stays that way until profiler_shutdown() is
|
||||
// called. Therefore it can be checked to determine if the profiler has been
|
||||
// initialized but not yet shut down.
|
||||
static PS* gPS = nullptr;
|
||||
|
||||
// The mutex that guards accesses to gPS.
|
||||
static PS::Mutex gPSMutex;
|
||||
static PSMutex gPSMutex;
|
||||
|
||||
// The name of the main thread.
|
||||
static const char* const kMainThreadName = "GeckoMain";
|
||||
|
@ -473,7 +472,7 @@ AddDynamicCodeLocationTag(ProfileBuffer* aBuffer, const char* aStr)
|
|||
static const int SAMPLER_MAX_STRING_LENGTH = 128;
|
||||
|
||||
static void
|
||||
AddPseudoEntry(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
AddPseudoEntry(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
volatile js::ProfileEntry& entry, PseudoStack* stack,
|
||||
void* lastpc)
|
||||
{
|
||||
|
@ -575,7 +574,7 @@ struct AutoWalkJSStack
|
|||
};
|
||||
|
||||
static void
|
||||
MergeStacksIntoProfile(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
MergeStacksIntoProfile(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
const TickSample& aSample, NativeStack& aNativeStack)
|
||||
{
|
||||
NotNull<PseudoStack*> pseudoStack = aSample.mPseudoStack;
|
||||
|
@ -759,7 +758,7 @@ MergeStacksIntoProfile(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
|||
// Update the JS context with the current profile sample buffer generation.
|
||||
//
|
||||
// Do not do this for synchronous samples, which use their own
|
||||
// ProfileBuffers instead of the global one in ProfilerState.
|
||||
// ProfileBuffers instead of the global one in PS.
|
||||
if (!aSample.mIsSynchronous && pseudoStack->mContext) {
|
||||
MOZ_ASSERT(aBuffer->mGeneration >= startBufferGen);
|
||||
uint32_t lapCount = aBuffer->mGeneration - startBufferGen;
|
||||
|
@ -785,7 +784,7 @@ StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
|
|||
}
|
||||
|
||||
static void
|
||||
DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
void* pc_array[1000];
|
||||
|
@ -827,7 +826,7 @@ DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
|||
|
||||
#ifdef USE_EHABI_STACKWALK
|
||||
static void
|
||||
DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
void* pc_array[1000];
|
||||
|
@ -916,7 +915,7 @@ ASAN_memcpy(void* aDst, const void* aSrc, size_t aLen)
|
|||
#endif
|
||||
|
||||
static void
|
||||
DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoNativeBacktrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
const mcontext_t* mc =
|
||||
|
@ -1033,10 +1032,10 @@ DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
|||
uintptr_t frameSPs[MAX_NATIVE_FRAMES];
|
||||
size_t framesAvail = mozilla::ArrayLength(framePCs);
|
||||
size_t framesUsed = 0;
|
||||
size_t scannedFramesAcquired = 0;
|
||||
size_t scannedFramesAcquired = 0, framePointerFramesAcquired = 0;
|
||||
lul::LUL* lul = gPS->LUL(aLock);
|
||||
lul->Unwind(&framePCs[0], &frameSPs[0],
|
||||
&framesUsed, &scannedFramesAcquired,
|
||||
&framesUsed, &framePointerFramesAcquired, &scannedFramesAcquired,
|
||||
framesAvail, scannedFramesAllowed,
|
||||
&startRegs, &stackImg);
|
||||
|
||||
|
@ -1052,14 +1051,16 @@ DoNativeBacktrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
|||
// Update stats in the LUL stats object. Unfortunately this requires
|
||||
// three global memory operations.
|
||||
lul->mStats.mContext += 1;
|
||||
lul->mStats.mCFI += framesUsed - 1 - scannedFramesAcquired;
|
||||
lul->mStats.mCFI += framesUsed - 1 - framePointerFramesAcquired -
|
||||
scannedFramesAcquired;
|
||||
lul->mStats.mFP += framePointerFramesAcquired;
|
||||
lul->mStats.mScanned += scannedFramesAcquired;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
DoSampleStackTrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
||||
DoSampleStackTrace(PSLockRef aLock, ProfileBuffer* aBuffer,
|
||||
const TickSample& aSample)
|
||||
{
|
||||
NativeStack nativeStack = { nullptr, nullptr, 0, 0 };
|
||||
|
@ -1073,11 +1074,12 @@ DoSampleStackTrace(PS::LockRef aLock, ProfileBuffer* aBuffer,
|
|||
// This function is called for each sampling period with the current program
|
||||
// counter. It is called within a signal and so must be re-entrant.
|
||||
static void
|
||||
Tick(PS::LockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
|
||||
Tick(PSLockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
|
||||
{
|
||||
aBuffer->addTagThreadId(aSample.mThreadId, aSample.mLastSample);
|
||||
|
||||
mozilla::TimeDuration delta = aSample.mTimeStamp - gPS->StartTime(aLock);
|
||||
mozilla::TimeDuration delta =
|
||||
aSample.mTimeStamp - gPS->ProcessStartTime(aLock);
|
||||
aBuffer->addTag(ProfileBufferEntry::Time(delta.ToMilliseconds()));
|
||||
|
||||
NotNull<PseudoStack*> pseudoStack = aSample.mPseudoStack;
|
||||
|
@ -1186,13 +1188,13 @@ StreamNameAndThreadId(JSONWriter& aWriter, const char* aName, int aThreadId)
|
|||
#endif
|
||||
|
||||
static void
|
||||
StreamTaskTracer(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
|
||||
StreamTaskTracer(PSLockRef aLock, SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
UniquePtr<nsTArray<nsCString>> data =
|
||||
mozilla::tasktracer::GetLoggedData(gPS->StartTime(aLock));
|
||||
mozilla::tasktracer::GetLoggedData(gPS->ProcessStartTime(aLock));
|
||||
for (uint32_t i = 0; i < data->Length(); ++i) {
|
||||
aWriter.StringElement((data->ElementAt(i)).get());
|
||||
}
|
||||
|
@ -1221,7 +1223,7 @@ StreamTaskTracer(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
|
|||
}
|
||||
|
||||
static void
|
||||
StreamMetaJSCustomObject(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
|
||||
StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
|
@ -1241,10 +1243,10 @@ StreamMetaJSCustomObject(PS::LockRef aLock, SpliceableJSONWriter& aWriter)
|
|||
aWriter.IntProperty("asyncstack", asyncStacks);
|
||||
|
||||
// The "startTime" field holds the number of milliseconds since midnight
|
||||
// January 1, 1970 GMT. This grotty code computes (Now - (Now - StartTime))
|
||||
// to convert gPS->StartTime() into that form.
|
||||
// January 1, 1970 GMT. This grotty code computes (Now - (Now -
|
||||
// ProcessStartTime)) to convert gPS->ProcessStartTime() into that form.
|
||||
mozilla::TimeDuration delta =
|
||||
mozilla::TimeStamp::Now() - gPS->StartTime(aLock);
|
||||
mozilla::TimeStamp::Now() - gPS->ProcessStartTime(aLock);
|
||||
aWriter.DoubleProperty(
|
||||
"startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
|
||||
|
||||
|
@ -1353,7 +1355,9 @@ BuildJavaThreadJSObject(SpliceableJSONWriter& aWriter)
|
|||
#endif
|
||||
|
||||
static void
|
||||
locked_profiler_stream_json_for_this_process(PS::LockRef aLock, SpliceableJSONWriter& aWriter, double aSinceTime)
|
||||
locked_profiler_stream_json_for_this_process(PSLockRef aLock,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
double aSinceTime)
|
||||
{
|
||||
LOG("locked_profiler_stream_json_for_this_process");
|
||||
|
||||
|
@ -1384,23 +1388,23 @@ locked_profiler_stream_json_for_this_process(PS::LockRef aLock, SpliceableJSONWr
|
|||
{
|
||||
gPS->SetIsPaused(aLock, true);
|
||||
|
||||
const PS::ThreadVector& liveThreads = gPS->LiveThreads(aLock);
|
||||
for (size_t i = 0; i < liveThreads.size(); i++) {
|
||||
ThreadInfo* info = liveThreads.at(i);
|
||||
if (!info->IsBeingProfiled()) {
|
||||
continue;
|
||||
}
|
||||
info->StreamJSON(gPS->Buffer(aLock), aWriter, gPS->StartTime(aLock),
|
||||
aSinceTime);
|
||||
const PS::ThreadVector& liveThreads = gPS->LiveThreads(aLock);
|
||||
for (size_t i = 0; i < liveThreads.size(); i++) {
|
||||
ThreadInfo* info = liveThreads.at(i);
|
||||
if (!info->IsBeingProfiled()) {
|
||||
continue;
|
||||
}
|
||||
info->StreamJSON(gPS->Buffer(aLock), aWriter,
|
||||
gPS->ProcessStartTime(aLock), aSinceTime);
|
||||
}
|
||||
|
||||
const PS::ThreadVector& deadThreads = gPS->DeadThreads(aLock);
|
||||
for (size_t i = 0; i < deadThreads.size(); i++) {
|
||||
ThreadInfo* info = deadThreads.at(i);
|
||||
MOZ_ASSERT(info->IsBeingProfiled());
|
||||
info->StreamJSON(gPS->Buffer(aLock), aWriter, gPS->StartTime(aLock),
|
||||
aSinceTime);
|
||||
}
|
||||
const PS::ThreadVector& deadThreads = gPS->DeadThreads(aLock);
|
||||
for (size_t i = 0; i < deadThreads.size(); i++) {
|
||||
ThreadInfo* info = deadThreads.at(i);
|
||||
MOZ_ASSERT(info->IsBeingProfiled());
|
||||
info->StreamJSON(gPS->Buffer(aLock), aWriter,
|
||||
gPS->ProcessStartTime(aLock), aSinceTime);
|
||||
}
|
||||
|
||||
#if defined(PROFILE_JAVA)
|
||||
if (gPS->FeatureJava(aLock)) {
|
||||
|
@ -1429,7 +1433,7 @@ profiler_stream_json_for_this_process(SpliceableJSONWriter& aWriter, double aSin
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return false;
|
||||
|
@ -1467,7 +1471,7 @@ ProfilerMarker::GetTime() const {
|
|||
}
|
||||
|
||||
void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aStartTime,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) const
|
||||
{
|
||||
// Schema:
|
||||
|
@ -1483,7 +1487,7 @@ void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter,
|
|||
if (mPayload) {
|
||||
aWriter.StartObjectElement();
|
||||
{
|
||||
mPayload->StreamPayload(aWriter, aStartTime, aUniqueStacks);
|
||||
mPayload->StreamPayload(aWriter, aProcessStartTime, aUniqueStacks);
|
||||
}
|
||||
aWriter.EndObject();
|
||||
}
|
||||
|
@ -1558,19 +1562,19 @@ class SamplerThread
|
|||
{
|
||||
public:
|
||||
// Creates a sampler thread, but doesn't start it.
|
||||
SamplerThread(PS::LockRef aLock, uint32_t aActivityGeneration,
|
||||
SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
double aIntervalMilliseconds);
|
||||
~SamplerThread();
|
||||
|
||||
// This runs on the sampler thread. It suspends and resumes the samplee
|
||||
// threads.
|
||||
void SuspendAndSampleAndResumeThread(PS::LockRef aLock, TickSample& aSample);
|
||||
void SuspendAndSampleAndResumeThread(PSLockRef aLock, TickSample& aSample);
|
||||
|
||||
// This runs on (is!) the sampler thread.
|
||||
void Run();
|
||||
|
||||
// This runs on the main thread.
|
||||
void Stop(PS::LockRef aLock);
|
||||
void Stop(PSLockRef aLock);
|
||||
|
||||
private:
|
||||
// The activity generation, for detecting when the sampler thread must stop.
|
||||
|
@ -1624,7 +1628,7 @@ SamplerThread::Run()
|
|||
while (true) {
|
||||
// This scope is for |lock|. It ends before we sleep below.
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// At this point profiler_stop() might have been called, and
|
||||
// profiler_start() might have been called on another thread.
|
||||
|
@ -1654,9 +1658,9 @@ SamplerThread::Run()
|
|||
// cheaper than taking a new sample.
|
||||
if (info->Stack()->CanDuplicateLastSampleDueToSleep()) {
|
||||
bool dup_ok =
|
||||
gPS->Buffer(lock)->DuplicateLastSample(info->ThreadId(),
|
||||
gPS->StartTime(lock),
|
||||
info->LastSample());
|
||||
gPS->Buffer(lock)->DuplicateLastSample(
|
||||
info->ThreadId(), gPS->ProcessStartTime(lock),
|
||||
info->LastSample());
|
||||
if (dup_ok) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1757,7 +1761,7 @@ GeckoProfilerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|||
#endif
|
||||
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (gPS) {
|
||||
profSize = GeckoProfilerMallocSizeOf(gPS);
|
||||
|
@ -1782,7 +1786,7 @@ GeckoProfilerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|||
// Measurement of the following things may be added later if DMD finds it
|
||||
// is worthwhile:
|
||||
// - gPS->mFeatures
|
||||
// - gPS->mThreadNameFilters
|
||||
// - gPS->mFilters
|
||||
// - gPS->mLiveThreads itself (its elements' children are measured above)
|
||||
// - gPS->mDeadThreads itself (ditto)
|
||||
// - gPS->mInterposeObserver
|
||||
|
@ -1796,8 +1800,8 @@ GeckoProfilerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|||
|
||||
MOZ_COLLECT_REPORT(
|
||||
"explicit/profiler/profiler-state", KIND_HEAP, UNITS_BYTES, profSize,
|
||||
"Memory used by the Gecko Profiler's ProfilerState object (excluding "
|
||||
"memory used by LUL).");
|
||||
"Memory used by the Gecko Profiler's global state (excluding memory used "
|
||||
"by LUL).");
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
MOZ_COLLECT_REPORT(
|
||||
|
@ -1811,23 +1815,23 @@ GeckoProfilerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|||
NS_IMPL_ISUPPORTS(GeckoProfilerReporter, nsIMemoryReporter)
|
||||
|
||||
static bool
|
||||
ThreadSelected(PS::LockRef aLock, const char* aThreadName)
|
||||
ThreadSelected(PSLockRef aLock, const char* aThreadName)
|
||||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
const Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(aLock);
|
||||
const Vector<std::string>& filters = gPS->Filters(aLock);
|
||||
|
||||
if (threadNameFilters.empty()) {
|
||||
if (filters.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string name = aThreadName;
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
for (uint32_t i = 0; i < threadNameFilters.length(); ++i) {
|
||||
std::string filter = threadNameFilters[i];
|
||||
for (uint32_t i = 0; i < filters.length(); ++i) {
|
||||
std::string filter = filters[i];
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
|
||||
// Crude, non UTF-8 compatible, case insensitive substring search
|
||||
|
@ -1840,7 +1844,7 @@ ThreadSelected(PS::LockRef aLock, const char* aThreadName)
|
|||
}
|
||||
|
||||
static bool
|
||||
ShouldProfileThread(PS::LockRef aLock, ThreadInfo* aInfo)
|
||||
ShouldProfileThread(PSLockRef aLock, ThreadInfo* aInfo)
|
||||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
|
@ -1853,7 +1857,7 @@ ShouldProfileThread(PS::LockRef aLock, ThreadInfo* aInfo)
|
|||
// Find the ThreadInfo for the current thread. On success, *aIndexOut is set to
|
||||
// the index if it is non-null.
|
||||
static ThreadInfo*
|
||||
FindLiveThreadInfo(PS::LockRef aLock, int* aIndexOut = nullptr)
|
||||
FindLiveThreadInfo(PSLockRef aLock, int* aIndexOut = nullptr)
|
||||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
|
@ -1872,7 +1876,7 @@ FindLiveThreadInfo(PS::LockRef aLock, int* aIndexOut = nullptr)
|
|||
}
|
||||
|
||||
static void
|
||||
locked_register_thread(PS::LockRef aLock, const char* aName, void* stackTop)
|
||||
locked_register_thread(PSLockRef aLock, const char* aName, void* stackTop)
|
||||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
|
@ -1906,7 +1910,7 @@ locked_register_thread(PS::LockRef aLock, const char* aName, void* stackTop)
|
|||
static void
|
||||
NotifyProfilerStarted(const int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
const char** aFilters, uint32_t aFilterCount)
|
||||
{
|
||||
if (!CanNotifyObservers()) {
|
||||
return;
|
||||
|
@ -1922,14 +1926,13 @@ NotifyProfilerStarted(const int aEntries, double aInterval,
|
|||
featuresArray.AppendElement(aFeatures[i]);
|
||||
}
|
||||
|
||||
nsTArray<nsCString> threadNameFiltersArray;
|
||||
nsTArray<nsCString> filtersArray;
|
||||
for (size_t i = 0; i < aFilterCount; ++i) {
|
||||
threadNameFiltersArray.AppendElement(aThreadNameFilters[i]);
|
||||
filtersArray.AppendElement(aFilters[i]);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIProfilerStartParams> params =
|
||||
new nsProfilerStartParams(aEntries, aInterval, featuresArray,
|
||||
threadNameFiltersArray);
|
||||
new nsProfilerStartParams(aEntries, aInterval, featuresArray, filtersArray);
|
||||
|
||||
os->NotifyObservers(params, "profiler-started", nullptr);
|
||||
}
|
||||
|
@ -1950,9 +1953,9 @@ NotifyObservers(const char* aTopic)
|
|||
}
|
||||
|
||||
static void
|
||||
locked_profiler_start(PS::LockRef aLock, const int aEntries, double aInterval,
|
||||
locked_profiler_start(PSLockRef aLock, const int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount);
|
||||
const char** aFilters, uint32_t aFilterCount);
|
||||
|
||||
void
|
||||
profiler_init(void* aStackTop)
|
||||
|
@ -1979,14 +1982,14 @@ profiler_init(void* aStackTop)
|
|||
}
|
||||
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// We've passed the possible failure point. Instantiate gPS, which
|
||||
// indicates that the profiler has initialized successfully.
|
||||
gPS = new PS();
|
||||
|
||||
bool ignore;
|
||||
gPS->SetStartTime(lock, mozilla::TimeStamp::ProcessCreation(ignore));
|
||||
gPS->SetProcessStartTime(lock, mozilla::TimeStamp::ProcessCreation(ignore));
|
||||
|
||||
locked_register_thread(lock, kMainThreadName, aStackTop);
|
||||
|
||||
|
@ -2052,10 +2055,10 @@ profiler_init(void* aStackTop)
|
|||
}
|
||||
|
||||
static void
|
||||
locked_profiler_save_profile_to_file(PS::LockRef aLock, const char* aFilename);
|
||||
locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename);
|
||||
|
||||
static SamplerThread*
|
||||
locked_profiler_stop(PS::LockRef aLock);
|
||||
locked_profiler_stop(PSLockRef aLock);
|
||||
|
||||
void
|
||||
profiler_shutdown()
|
||||
|
@ -2069,7 +2072,7 @@ profiler_shutdown()
|
|||
// gPS is destroyed, in order to delete it.
|
||||
SamplerThread* samplerThread = nullptr;
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// Save the profile on shutdown if requested.
|
||||
if (gPS->IsActive(lock)) {
|
||||
|
@ -2160,7 +2163,7 @@ profiler_get_start_params(int* aEntries, double* aInterval,
|
|||
return;
|
||||
}
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
*aEntries = gPS->Entries(lock);
|
||||
*aInterval = gPS->Interval(lock);
|
||||
|
@ -2171,15 +2174,15 @@ profiler_get_start_params(int* aEntries, double* aInterval,
|
|||
(*aFeatures)[i] = features[i].c_str();
|
||||
}
|
||||
|
||||
const Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(lock);
|
||||
MOZ_ALWAYS_TRUE(aFilters->resize(threadNameFilters.length()));
|
||||
for (uint32_t i = 0; i < threadNameFilters.length(); ++i) {
|
||||
(*aFilters)[i] = threadNameFilters[i].c_str();
|
||||
const Vector<std::string>& filters = gPS->Filters(lock);
|
||||
MOZ_ALWAYS_TRUE(aFilters->resize(filters.length()));
|
||||
for (uint32_t i = 0; i < filters.length(); ++i) {
|
||||
(*aFilters)[i] = filters[i].c_str();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
locked_profiler_save_profile_to_file(PS::LockRef aLock, const char* aFilename)
|
||||
locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename)
|
||||
{
|
||||
LOG("locked_profiler_save_profile_to_file(%s)", aFilename);
|
||||
|
||||
|
@ -2213,7 +2216,7 @@ profiler_save_profile_to_file(const char* aFilename)
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return;
|
||||
|
@ -2277,7 +2280,7 @@ profiler_get_buffer_info_helper(uint32_t* aCurrentPosition,
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return;
|
||||
|
@ -2300,9 +2303,9 @@ hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature)
|
|||
}
|
||||
|
||||
static void
|
||||
locked_profiler_start(PS::LockRef aLock, int aEntries, double aInterval,
|
||||
locked_profiler_start(PSLockRef aLock, int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
const char** aFilters, uint32_t aFilterCount)
|
||||
{
|
||||
if (LOG_TEST) {
|
||||
LOG("locked_profiler_start");
|
||||
|
@ -2312,16 +2315,13 @@ locked_profiler_start(PS::LockRef aLock, int aEntries, double aInterval,
|
|||
LOG("- feature = %s", aFeatures[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < aFilterCount; i++) {
|
||||
LOG("- threads = %s", aThreadNameFilters[i]);
|
||||
LOG("- threads = %s", aFilters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS && !gPS->IsActive(aLock));
|
||||
|
||||
bool ignore;
|
||||
gPS->SetStartTime(aLock, mozilla::TimeStamp::ProcessCreation(ignore));
|
||||
|
||||
// Fall back to the default value if the passed-in value is unreasonable.
|
||||
int entries = aEntries > 0 ? aEntries : PROFILE_DEFAULT_ENTRIES;
|
||||
gPS->SetEntries(aLock, entries);
|
||||
|
@ -2337,12 +2337,11 @@ locked_profiler_start(PS::LockRef aLock, int aEntries, double aInterval,
|
|||
features[i] = aFeatures[i];
|
||||
}
|
||||
|
||||
// Deep copy aThreadNameFilters. Must precede the ShouldProfileThread() call
|
||||
// below.
|
||||
Vector<std::string>& threadNameFilters = gPS->ThreadNameFilters(aLock);
|
||||
MOZ_ALWAYS_TRUE(threadNameFilters.resize(aFilterCount));
|
||||
// Deep copy aFilters. Must precede the ShouldProfileThread() call below.
|
||||
Vector<std::string>& filters = gPS->Filters(aLock);
|
||||
MOZ_ALWAYS_TRUE(filters.resize(aFilterCount));
|
||||
for (uint32_t i = 0; i < aFilterCount; ++i) {
|
||||
threadNameFilters[i] = aThreadNameFilters[i];
|
||||
filters[i] = aFilters[i];
|
||||
}
|
||||
|
||||
#define HAS_FEATURE(feature) hasFeature(aFeatures, aFeatureCount, feature)
|
||||
|
@ -2441,7 +2440,7 @@ locked_profiler_start(PS::LockRef aLock, int aEntries, double aInterval,
|
|||
void
|
||||
profiler_start(int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
const char** aFilters, uint32_t aFilterCount)
|
||||
{
|
||||
LOG("profiler_start");
|
||||
|
||||
|
@ -2449,7 +2448,7 @@ profiler_start(int aEntries, double aInterval,
|
|||
|
||||
SamplerThread* samplerThread = nullptr;
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// Initialize if necessary.
|
||||
if (!gPS) {
|
||||
|
@ -2462,7 +2461,7 @@ profiler_start(int aEntries, double aInterval,
|
|||
}
|
||||
|
||||
locked_profiler_start(lock, aEntries, aInterval, aFeatures, aFeatureCount,
|
||||
aThreadNameFilters, aFilterCount);
|
||||
aFilters, aFilterCount);
|
||||
}
|
||||
|
||||
// We do these operations with gPSMutex unlocked. The comments in
|
||||
|
@ -2472,11 +2471,11 @@ profiler_start(int aEntries, double aInterval,
|
|||
delete samplerThread;
|
||||
}
|
||||
NotifyProfilerStarted(aEntries, aInterval, aFeatures, aFeatureCount,
|
||||
aThreadNameFilters, aFilterCount);
|
||||
aFilters, aFilterCount);
|
||||
}
|
||||
|
||||
static MOZ_MUST_USE SamplerThread*
|
||||
locked_profiler_stop(PS::LockRef aLock)
|
||||
locked_profiler_stop(PSLockRef aLock)
|
||||
{
|
||||
LOG("locked_profiler_stop");
|
||||
|
||||
|
@ -2554,7 +2553,7 @@ locked_profiler_stop(PS::LockRef aLock)
|
|||
gPS->SetFeatureTaskTracer(aLock, false);
|
||||
gPS->SetFeatureThreads(aLock, false);
|
||||
|
||||
gPS->ThreadNameFilters(aLock).clear();
|
||||
gPS->Filters(aLock).clear();
|
||||
|
||||
gPS->Features(aLock).clear();
|
||||
|
||||
|
@ -2575,7 +2574,7 @@ profiler_stop()
|
|||
|
||||
SamplerThread* samplerThread;
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return;
|
||||
|
@ -2607,7 +2606,7 @@ profiler_is_paused()
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return false;
|
||||
|
@ -2625,7 +2624,7 @@ profiler_pause()
|
|||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return;
|
||||
|
@ -2646,7 +2645,7 @@ profiler_resume()
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
{
|
||||
if (!gPS->IsActive(lock)) {
|
||||
|
@ -2667,7 +2666,7 @@ profiler_feature_active(const char* aName)
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock)) {
|
||||
return false;
|
||||
|
@ -2699,7 +2698,7 @@ profiler_is_active()
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
return gPS->IsActive(lock);
|
||||
}
|
||||
|
@ -2711,7 +2710,7 @@ profiler_set_frame_number(int aFrameNumber)
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
gPS->SetFrameNumber(lock, aFrameNumber);
|
||||
}
|
||||
|
@ -2724,7 +2723,7 @@ profiler_register_thread(const char* aName, void* aGuessStackTop)
|
|||
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
void* stackTop = GetStackTop(aGuessStackTop);
|
||||
locked_register_thread(lock, aName, stackTop);
|
||||
|
@ -2736,7 +2735,7 @@ profiler_unregister_thread()
|
|||
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// We don't call PseudoStack::stopJSSampling() here; there's no point doing
|
||||
// that for a JS thread that is in the process of disappearing.
|
||||
|
@ -2834,10 +2833,10 @@ profiler_time()
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
mozilla::TimeDuration delta =
|
||||
mozilla::TimeStamp::Now() - gPS->StartTime(lock);
|
||||
mozilla::TimeStamp::Now() - gPS->ProcessStartTime(lock);
|
||||
return delta.ToMilliseconds();
|
||||
}
|
||||
|
||||
|
@ -2847,7 +2846,7 @@ profiler_get_backtrace()
|
|||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
|
||||
return nullptr;
|
||||
|
@ -2910,7 +2909,7 @@ profiler_get_backtrace_noalloc(char *output, size_t outputSize)
|
|||
|
||||
bool includeDynamicString = true;
|
||||
{
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
includeDynamicString = !gPS->FeaturePrivacy(lock);
|
||||
}
|
||||
|
||||
|
@ -2947,7 +2946,7 @@ profiler_get_backtrace_noalloc(char *output, size_t outputSize)
|
|||
}
|
||||
|
||||
static void
|
||||
locked_profiler_add_marker(PS::LockRef aLock, const char* aMarker,
|
||||
locked_profiler_add_marker(PSLockRef aLock, const char* aMarker,
|
||||
ProfilerMarkerPayload* aPayload)
|
||||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
@ -2966,7 +2965,7 @@ locked_profiler_add_marker(PS::LockRef aLock, const char* aMarker,
|
|||
mozilla::TimeStamp origin = (payload && !payload->GetStartTime().IsNull())
|
||||
? payload->GetStartTime()
|
||||
: mozilla::TimeStamp::Now();
|
||||
mozilla::TimeDuration delta = origin - gPS->StartTime(aLock);
|
||||
mozilla::TimeDuration delta = origin - gPS->ProcessStartTime(aLock);
|
||||
stack->addMarker(aMarker, payload.release(), delta.ToMilliseconds());
|
||||
}
|
||||
|
||||
|
@ -2977,7 +2976,7 @@ profiler_add_marker(const char* aMarker, ProfilerMarkerPayload* aPayload)
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
// aPayload must be freed if we return early.
|
||||
mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
|
||||
|
@ -2997,7 +2996,7 @@ profiler_tracing(const char* aCategory, const char* aInfo,
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
|
||||
return;
|
||||
|
@ -3015,7 +3014,7 @@ profiler_tracing(const char* aCategory, const char* aInfo,
|
|||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!gPS->IsActive(lock) || gPS->FeaturePrivacy(lock)) {
|
||||
return;
|
||||
|
@ -3068,7 +3067,7 @@ profiler_clear_js_context()
|
|||
// On JS shut down, flush the current buffer as stringifying JIT samples
|
||||
// requires a live JSContext.
|
||||
|
||||
PS::AutoLock lock(gPSMutex);
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (gPS->IsActive(lock)) {
|
||||
gPS->SetIsPaused(lock, true);
|
||||
|
@ -3077,7 +3076,8 @@ profiler_clear_js_context()
|
|||
ThreadInfo* info = FindLiveThreadInfo(lock);
|
||||
MOZ_RELEASE_ASSERT(info);
|
||||
if (info->IsBeingProfiled()) {
|
||||
info->FlushSamplesAndMarkers(gPS->Buffer(lock), gPS->StartTime(lock));
|
||||
info->FlushSamplesAndMarkers(gPS->Buffer(lock),
|
||||
gPS->ProcessStartTime(lock));
|
||||
}
|
||||
|
||||
gPS->SetIsPaused(lock, false);
|
||||
|
|
|
@ -896,13 +896,14 @@ LUL::MaybeShowStats()
|
|||
if (n_new >= 5000) {
|
||||
uint32_t n_new_Context = mStats.mContext - mStatsPrevious.mContext;
|
||||
uint32_t n_new_CFI = mStats.mCFI - mStatsPrevious.mCFI;
|
||||
uint32_t n_new_FP = mStats.mFP - mStatsPrevious.mFP;
|
||||
uint32_t n_new_Scanned = mStats.mScanned - mStatsPrevious.mScanned;
|
||||
mStatsPrevious = mStats;
|
||||
char buf[200];
|
||||
SprintfLiteral(buf,
|
||||
"LUL frame stats: TOTAL %5u"
|
||||
" CTX %4u CFI %4u SCAN %4u",
|
||||
n_new, n_new_Context, n_new_CFI, n_new_Scanned);
|
||||
" CTX %4u CFI %4u FP %4u SCAN %4u",
|
||||
n_new, n_new_Context, n_new_CFI, n_new_FP, n_new_Scanned);
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
mLog(buf);
|
||||
}
|
||||
|
@ -1346,6 +1347,7 @@ void
|
|||
LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
|
||||
/*OUT*/uintptr_t* aFrameSPs,
|
||||
/*OUT*/size_t* aFramesUsed,
|
||||
/*OUT*/size_t* aFramePointerFramesAcquired,
|
||||
/*OUT*/size_t* aScannedFramesAcquired,
|
||||
size_t aFramesAvail,
|
||||
size_t aScannedFramesAllowed,
|
||||
|
@ -1545,8 +1547,62 @@ LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
|
|||
|
||||
} else {
|
||||
|
||||
// There's no RuleSet for the specified address, so see if
|
||||
// it's possible to get anywhere by stack-scanning.
|
||||
// There's no RuleSet for the specified address. On amd64_linux, see if
|
||||
// it's possible to recover the caller's frame by using the frame pointer.
|
||||
// This would probably work for the 32-bit case too, but hasn't been
|
||||
// tested for that case.
|
||||
|
||||
#if defined(GP_PLAT_amd64_linux)
|
||||
// We seek to compute (new_IP, new_SP, new_BP) from (old_BP, stack image),
|
||||
// and assume the following layout:
|
||||
//
|
||||
// <--- new_SP
|
||||
// +----------+
|
||||
// | new_IP | (return address)
|
||||
// +----------+
|
||||
// | new_BP | <--- old_BP
|
||||
// +----------+
|
||||
// | .... |
|
||||
// | .... |
|
||||
// | .... |
|
||||
// +----------+ <---- old_SP (arbitrary, but must be <= old_BP)
|
||||
|
||||
const size_t wordSzB = sizeof(uintptr_t);
|
||||
TaggedUWord old_xsp = regs.xsp;
|
||||
|
||||
// points at new_BP ?
|
||||
TaggedUWord old_xbp = regs.xbp;
|
||||
// points at new_IP ?
|
||||
TaggedUWord old_xbp_plus1 = regs.xbp + TaggedUWord(1 * wordSzB);
|
||||
// is the new_SP ?
|
||||
TaggedUWord old_xbp_plus2 = regs.xbp + TaggedUWord(2 * wordSzB);
|
||||
|
||||
if (old_xbp.Valid() && old_xbp.IsAligned() &&
|
||||
old_xsp.Valid() && old_xsp.IsAligned() &&
|
||||
old_xsp.Value() <= old_xbp.Value()) {
|
||||
// We don't need to do any range, alignment or validity checks for
|
||||
// addresses passed to DerefTUW, since that performs them itself, and
|
||||
// returns an invalid value on failure. Any such value will poison
|
||||
// subsequent uses, and we do a final check for validity before putting
|
||||
// the computed values into |regs|.
|
||||
TaggedUWord new_xbp = DerefTUW(old_xbp, aStackImg);
|
||||
if (new_xbp.Valid() && new_xbp.IsAligned() &&
|
||||
old_xbp.Value() < new_xbp.Value()) {
|
||||
TaggedUWord new_xip = DerefTUW(old_xbp_plus1, aStackImg);
|
||||
TaggedUWord new_xsp = old_xbp_plus2;
|
||||
if (new_xbp.Valid() && new_xip.Valid() && new_xsp.Valid()) {
|
||||
regs.xbp = new_xbp;
|
||||
regs.xip = new_xip;
|
||||
regs.xsp = new_xsp;
|
||||
(*aFramePointerFramesAcquired)++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// As a last-ditch resort, see if it's possible to get anywhere by
|
||||
// stack-scanning.
|
||||
|
||||
// Use stack scanning frugally.
|
||||
if (n_scanned_frames++ >= aScannedFramesAllowed) {
|
||||
|
@ -1768,9 +1824,10 @@ bool GetAndCheckStackTrace(LUL* aLUL, const char* dstring)
|
|||
size_t framesAvail = mozilla::ArrayLength(framePCs);
|
||||
size_t framesUsed = 0;
|
||||
size_t scannedFramesAllowed = 0;
|
||||
size_t scannedFramesAcquired = 0;
|
||||
size_t scannedFramesAcquired = 0, framePointerFramesAcquired = 0;
|
||||
aLUL->Unwind( &framePCs[0], &frameSPs[0],
|
||||
&framesUsed, &scannedFramesAcquired,
|
||||
&framesUsed,
|
||||
&framePointerFramesAcquired, &scannedFramesAcquired,
|
||||
framesAvail, scannedFramesAllowed,
|
||||
&startRegs, stackImg );
|
||||
|
||||
|
|
|
@ -183,6 +183,7 @@ public:
|
|||
LULStats()
|
||||
: mContext(0)
|
||||
, mCFI(0)
|
||||
, mFP(0)
|
||||
, mScanned(0)
|
||||
{}
|
||||
|
||||
|
@ -190,6 +191,7 @@ public:
|
|||
explicit LULStats(const LULStats<S>& aOther)
|
||||
: mContext(aOther.mContext)
|
||||
, mCFI(aOther.mCFI)
|
||||
, mFP(aOther.mFP)
|
||||
, mScanned(aOther.mScanned)
|
||||
{}
|
||||
|
||||
|
@ -198,6 +200,7 @@ public:
|
|||
{
|
||||
mContext = aOther.mContext;
|
||||
mCFI = aOther.mCFI;
|
||||
mFP = aOther.mFP;
|
||||
mScanned = aOther.mScanned;
|
||||
return *this;
|
||||
}
|
||||
|
@ -205,11 +208,13 @@ public:
|
|||
template <typename S>
|
||||
uint32_t operator-(const LULStats<S>& aOther) {
|
||||
return (mContext - aOther.mContext) +
|
||||
(mCFI - aOther.mCFI) + (mScanned - aOther.mScanned);
|
||||
(mCFI - aOther.mCFI) + (mFP - aOther.mFP) +
|
||||
(mScanned - aOther.mScanned);
|
||||
}
|
||||
|
||||
T mContext; // Number of context frames
|
||||
T mCFI; // Number of CFI/EXIDX frames
|
||||
T mFP; // Number of frame-pointer recovered frames
|
||||
T mScanned; // Number of scanned frames
|
||||
};
|
||||
|
||||
|
@ -338,6 +343,7 @@ public:
|
|||
void Unwind(/*OUT*/uintptr_t* aFramePCs,
|
||||
/*OUT*/uintptr_t* aFrameSPs,
|
||||
/*OUT*/size_t* aFramesUsed,
|
||||
/*OUT*/size_t* aFramePointerFramesAcquired,
|
||||
/*OUT*/size_t* aScannedFramesAcquired,
|
||||
size_t aFramesAvail,
|
||||
size_t aScannedFramesAllowed,
|
||||
|
|
|
@ -289,8 +289,8 @@ PROFILER_FUNC(bool profiler_thread_is_sleeping(), false)
|
|||
// not.
|
||||
PROFILER_FUNC_VOID(profiler_js_interrupt_callback())
|
||||
|
||||
// Gets the time since the last profiler_init() or profiler_start() call.
|
||||
// Operates the same whether the profiler is active or inactive.
|
||||
// The number of milliseconds since the process started. Operates the same
|
||||
// whether the profiler is active or inactive.
|
||||
PROFILER_FUNC(double profiler_time(), 0)
|
||||
|
||||
PROFILER_FUNC_VOID(profiler_log(const char *str))
|
||||
|
@ -329,14 +329,6 @@ class ProfilerMarkerPayload;
|
|||
//
|
||||
extern MOZ_THREAD_LOCAL(PseudoStack*) tlsPseudoStack;
|
||||
|
||||
class ProfilerState;
|
||||
|
||||
// The core profiler state. Null at process startup, it is set to a non-null
|
||||
// value in profiler_init() and stays that way until profiler_shutdown() is
|
||||
// called. Therefore it can be checked to determine if the profiler has been
|
||||
// initialized but not yet shut down.
|
||||
extern ProfilerState* gPS;
|
||||
|
||||
#ifndef SAMPLE_FUNCTION_NAME
|
||||
# if defined(__GNUC__) || defined(_MSC_VER)
|
||||
# define SAMPLE_FUNCTION_NAME __FUNCTION__
|
||||
|
@ -355,8 +347,6 @@ profiler_call_enter(const char* aInfo,
|
|||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
PseudoStack* stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
return stack;
|
||||
|
@ -373,8 +363,6 @@ profiler_call_exit(void* aHandle)
|
|||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
if (!aHandle) {
|
||||
return;
|
||||
}
|
||||
|
@ -498,8 +486,6 @@ profiler_get_pseudo_stack(void)
|
|||
{
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(gPS);
|
||||
|
||||
return tlsPseudoStack.get();
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче