mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge f-t to m-c, a=merge
This commit is contained in:
commit
45d1b8a22f
@ -1879,7 +1879,7 @@ pref("browser.polaris.enabled", false);
|
||||
pref("privacy.trackingprotection.ui.enabled", false);
|
||||
#endif
|
||||
pref("privacy.trackingprotection.introCount", 0);
|
||||
pref("privacy.trackingprotection.introURL", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/tracking-protection-pbm");
|
||||
pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
|
||||
|
||||
#ifndef RELEASE_BUILD
|
||||
// At the moment, autostart.2 is used, while autostart.1 is unused.
|
||||
|
@ -134,6 +134,9 @@ loop.conversation = (function(mozL10n) {
|
||||
mozLoop: navigator.mozLoop
|
||||
});
|
||||
|
||||
// expose for functional tests
|
||||
loop.conversation._sdkDriver = sdkDriver;
|
||||
|
||||
// Create the stores.
|
||||
var conversationAppStore = new loop.store.ConversationAppStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -134,6 +134,9 @@ loop.conversation = (function(mozL10n) {
|
||||
mozLoop: navigator.mozLoop
|
||||
});
|
||||
|
||||
// expose for functional tests
|
||||
loop.conversation._sdkDriver = sdkDriver;
|
||||
|
||||
// Create the stores.
|
||||
var conversationAppStore = new loop.store.ConversationAppStore({
|
||||
dispatcher: dispatcher,
|
||||
|
@ -457,17 +457,37 @@ html[dir="rtl"] .room-failure > .settings-control {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.screen-share-menu.dropdown-menu,
|
||||
.settings-menu.dropdown-menu {
|
||||
left: auto;
|
||||
bottom: 3.1rem;
|
||||
}
|
||||
|
||||
.screen-share-menu.dropdown-menu {
|
||||
/*offset dropdown menu to be above menu button*/
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.settings-menu.dropdown-menu {
|
||||
/*offset dropdown menu to be above menu button*/
|
||||
right: 14px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .screen-share-menu.dropdown-menu,
|
||||
html[dir="rtl"] .settings-menu.dropdown-menu {
|
||||
left: 14px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .screen-share-menu.dropdown-menu {
|
||||
/*offset dropdown menu to be above menu button*/
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .settings-menu.dropdown-menu {
|
||||
/*offset dropdown menu to be above menu button*/
|
||||
left: 14px;
|
||||
}
|
||||
|
||||
.settings-menu.dropdown-menu.menu-below {
|
||||
top: 11.5rem;
|
||||
bottom: auto;
|
||||
|
@ -150,12 +150,12 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
"disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
|
||||
});
|
||||
var dropdownMenuClasses = cx({
|
||||
"native-dropdown-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
"hide": !this.state.showMenu,
|
||||
"visually-hidden": true
|
||||
"screen-share-menu": true,
|
||||
"dropdown-menu": true,
|
||||
"hide": !this.state.showMenu
|
||||
});
|
||||
var windowSharingClasses = cx({
|
||||
"dropdown-menu-item": true,
|
||||
"disabled": this.state.windowSharingDisabled
|
||||
});
|
||||
|
||||
@ -168,7 +168,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
isActive ? null : React.createElement("span", {className: "chevron"})
|
||||
),
|
||||
React.createElement("ul", {className: dropdownMenuClasses, ref: "menu"},
|
||||
React.createElement("li", {onClick: this._handleShareTabs},
|
||||
React.createElement("li", {className: "dropdown-menu-item", onClick: this._handleShareTabs},
|
||||
mozL10n.get("share_tabs_button_title2")
|
||||
),
|
||||
React.createElement("li", {className: windowSharingClasses, onClick: this._handleShareWindows},
|
||||
|
@ -150,12 +150,12 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
"disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
|
||||
});
|
||||
var dropdownMenuClasses = cx({
|
||||
"native-dropdown-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
"hide": !this.state.showMenu,
|
||||
"visually-hidden": true
|
||||
"screen-share-menu": true,
|
||||
"dropdown-menu": true,
|
||||
"hide": !this.state.showMenu
|
||||
});
|
||||
var windowSharingClasses = cx({
|
||||
"dropdown-menu-item": true,
|
||||
"disabled": this.state.windowSharingDisabled
|
||||
});
|
||||
|
||||
@ -168,7 +168,7 @@ loop.shared.views = (function(_, mozL10n) {
|
||||
{isActive ? null : <span className="chevron"/>}
|
||||
</button>
|
||||
<ul className={dropdownMenuClasses} ref="menu">
|
||||
<li onClick={this._handleShareTabs}>
|
||||
<li className="dropdown-menu-item" onClick={this._handleShareTabs}>
|
||||
{mozL10n.get("share_tabs_button_title2")}
|
||||
</li>
|
||||
<li className={windowSharingClasses} onClick={this._handleShareWindows}>
|
||||
|
@ -169,7 +169,7 @@ describe("loop.shared.views", function() {
|
||||
}));
|
||||
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(
|
||||
".conversation-window-dropdown > li"));
|
||||
".screen-share-menu > li"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
@ -186,7 +186,7 @@ describe("loop.shared.views", function() {
|
||||
}));
|
||||
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(
|
||||
".conversation-window-dropdown > li:last-child"));
|
||||
".screen-share-menu > li:last-child"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
@ -201,7 +201,7 @@ describe("loop.shared.views", function() {
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
|
||||
var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
|
||||
expect(node.classList.contains("disabled")).eql(false);
|
||||
});
|
||||
|
||||
@ -216,7 +216,7 @@ describe("loop.shared.views", function() {
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
|
||||
var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
|
||||
expect(node.classList.contains("disabled")).eql(true);
|
||||
});
|
||||
|
||||
@ -231,7 +231,7 @@ describe("loop.shared.views", function() {
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
|
||||
var node = comp.getDOMNode().querySelector(".conversation-window-dropdown > li:last-child");
|
||||
var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
|
||||
expect(node.classList.contains("disabled")).eql(true);
|
||||
});
|
||||
|
||||
|
@ -335,6 +335,7 @@
|
||||
<method name="handleSearchCommand">
|
||||
<parameter name="aEvent"/>
|
||||
<parameter name="aEngine"/>
|
||||
<parameter name="aForceNewTab"/>
|
||||
<body><![CDATA[
|
||||
var textBox = this._textbox;
|
||||
var textValue = textBox.value;
|
||||
@ -347,6 +348,11 @@
|
||||
return;
|
||||
where = whereToOpenLink(aEvent, false, true);
|
||||
}
|
||||
else if (aForceNewTab) {
|
||||
where = "tab";
|
||||
if (Services.prefs.getBoolPref("browser.tabs.loadInBackground"))
|
||||
where += "-background";
|
||||
}
|
||||
else {
|
||||
var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
|
||||
if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref)
|
||||
@ -380,6 +386,8 @@
|
||||
} else if (aEvent instanceof XULCommandEvent) {
|
||||
if (target.getAttribute("anonid") == "paste-and-search") {
|
||||
source = "paste";
|
||||
} else if (target.getAttribute("anonid") == "search-one-offs-context-open-in-new-tab") {
|
||||
source = "oneoff-context";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1007,7 +1015,7 @@
|
||||
<resources>
|
||||
<stylesheet src="chrome://browser/skin/searchbar.css"/>
|
||||
</resources>
|
||||
<content ignorekeys="true" level="top" consumeoutsideclicks="never">
|
||||
<content ignorekeys="true" level="top" consumeoutsideclicks="never" context="_child">
|
||||
<xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
|
||||
class="search-panel-header search-panel-current-engine">
|
||||
<xul:image class="searchbar-engine-image" xbl:inherits="src"/>
|
||||
@ -1049,6 +1057,14 @@
|
||||
oncommand="showSettings();"
|
||||
class="search-setting-button search-panel-header"
|
||||
label="&changeSearchSettings.button;"/>
|
||||
<xul:menupopup anonid="search-one-offs-context-menu">
|
||||
<xul:menuitem anonid="search-one-offs-context-open-in-new-tab"
|
||||
label="&searchInNewTab.label;"
|
||||
accesskey="&searchInNewTab.accesskey;"/>
|
||||
<xul:menuitem anonid="search-one-offs-context-set-default"
|
||||
label="&searchSetAsDefault.label;"
|
||||
accesskey="&searchSetAsDefault.accesskey;"/>
|
||||
</xul:menupopup>
|
||||
</content>
|
||||
<implementation>
|
||||
<!-- Popup rollup is triggered by native events before the mousedown event
|
||||
@ -1056,6 +1072,9 @@
|
||||
false after the mousedown event has been triggered to detect what
|
||||
caused rollup. -->
|
||||
<field name="_isHiding">false</field>
|
||||
<!-- When a context menu is opened on a one-off button, this is set to the
|
||||
engine of that button for use with the context menu actions. -->
|
||||
<field name="_contextEngine">null</field>
|
||||
<field name="_bundle">null</field>
|
||||
<property name="bundle" readonly="true">
|
||||
<getter>
|
||||
@ -1101,12 +1120,41 @@
|
||||
BrowserSearch.searchBar._textbox.closePopup();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<constructor><![CDATA[
|
||||
// Prevent popup events from the context menu from reaching the autocomplete
|
||||
// binding (or other listeners).
|
||||
let menu = document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-menu");
|
||||
let listener = aEvent => aEvent.stopPropagation();
|
||||
menu.addEventListener("popupshowing", listener);
|
||||
menu.addEventListener("popuphiding", listener);
|
||||
menu.addEventListener("popupshown", aEvent => {
|
||||
this._ignoreMouseEvents = true;
|
||||
aEvent.stopPropagation();
|
||||
});
|
||||
menu.addEventListener("popuphidden", aEvent => {
|
||||
this._ignoreMouseEvents = false;
|
||||
aEvent.stopPropagation();
|
||||
});
|
||||
]]></constructor>
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="popuphidden"><![CDATA[
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
document.getElementById("searchbar").textbox.selectedButton = null;
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
this._contextEngine = null;
|
||||
]]></handler>
|
||||
|
||||
<handler event="contextmenu"><![CDATA[
|
||||
let target = event.originalTarget;
|
||||
// Prevent the context menu from appearing except on the one off buttons.
|
||||
if (!target.classList.contains("searchbar-engine-one-off-item") ||
|
||||
target.classList.contains("dummy")) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
this._contextEngine = target.engine;
|
||||
]]></handler>
|
||||
|
||||
<handler event="popupshowing"><![CDATA[
|
||||
@ -1321,6 +1369,10 @@
|
||||
if (target.localName != "button")
|
||||
return;
|
||||
|
||||
// Ignore mouse events when the context menu is open.
|
||||
if (this._ignoreMouseEvents)
|
||||
return;
|
||||
|
||||
if ((target.classList.contains("searchbar-engine-one-off-item") &&
|
||||
!target.classList.contains("dummy")) ||
|
||||
target.classList.contains("addengine-item") ||
|
||||
@ -1336,6 +1388,10 @@
|
||||
if (target.localName != "button")
|
||||
return;
|
||||
|
||||
// Don't deselect the current button if the context menu is open.
|
||||
if (this._ignoreMouseEvents)
|
||||
return;
|
||||
|
||||
let textbox = document.getElementById("searchbar").textbox;
|
||||
if (textbox.selectedButton == target)
|
||||
textbox.selectedButton = null;
|
||||
@ -1351,6 +1407,14 @@
|
||||
if (!engine)
|
||||
return;
|
||||
|
||||
// For some reason, if the context menu had been opened prior to the
|
||||
// click, the suggestions popup won't be closed after loading the search
|
||||
// in the current tab - so we hide it manually. Some focusing magic
|
||||
// that happens when a search is loaded ensures that the popup is opened
|
||||
// again if it needs to be, so we don't need to worry about which cases
|
||||
// require manual hiding.
|
||||
this.hidePopup();
|
||||
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
searchbar.handleSearchCommand(event, engine);
|
||||
]]></handler>
|
||||
@ -1373,6 +1437,29 @@
|
||||
target.getAttribute("image"), false,
|
||||
installCallback);
|
||||
}
|
||||
let anonid = target.getAttribute("anonid");
|
||||
if (anonid == "search-one-offs-context-open-in-new-tab") {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
searchbar.handleSearchCommand(event, this._contextEngine, true);
|
||||
}
|
||||
if (anonid == "search-one-offs-context-set-default") {
|
||||
let currentEngine = Services.search.currentEngine;
|
||||
|
||||
// Make the target button of the context menu reflect the current
|
||||
// search engine first. Doing this as opposed to rebuilding all the
|
||||
// one-off buttons avoids flicker.
|
||||
let button = document.getElementById("searchbar-engine-one-off-item-" +
|
||||
this._contextEngine.name.replace(/ /g, '-'));
|
||||
button.id = "searchbar-engine-one-off-item-" + currentEngine.name.replace(/ /g, '-');
|
||||
let uri = "chrome://browser/skin/search-engine-placeholder.png";
|
||||
if (currentEngine.iconURI)
|
||||
uri = PlacesUtils.getImageURLForResolution(window, currentEngine.iconURI.spec);
|
||||
button.setAttribute("image", uri);
|
||||
button.setAttribute("tooltiptext", currentEngine.name);
|
||||
button.engine = currentEngine;
|
||||
|
||||
Services.search.currentEngine = this._contextEngine;
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
<handler event="popuphiding"><![CDATA[
|
||||
|
@ -3,43 +3,15 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const constants = require('../constants');
|
||||
const promise = require('promise');
|
||||
const { rdpInvoke, asPaused } = require('../utils');
|
||||
const constants = require("../constants");
|
||||
const { rdpInvoke, asPaused } = require("../utils");
|
||||
const { reportException } = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
|
||||
|
||||
const initialState = {
|
||||
activeEventNames: [],
|
||||
listeners: [],
|
||||
fetchingListeners: false,
|
||||
};
|
||||
|
||||
function update(state = initialState, action, emitChange) {
|
||||
switch(action.type) {
|
||||
case constants.UPDATE_EVENT_BREAKPOINTS:
|
||||
state.activeEventNames = action.eventNames;
|
||||
emitChange('activeEventNames', state.activeEventNames);
|
||||
break;
|
||||
case constants.FETCH_EVENT_LISTENERS:
|
||||
if (action.status === "begin") {
|
||||
state.fetchingListeners = true;
|
||||
}
|
||||
else if (action.status === "done") {
|
||||
state.fetchingListeners = false;
|
||||
state.listeners = action.listeners;
|
||||
emitChange('listeners', state.listeners);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
function fetchEventListeners() {
|
||||
return (dispatch, getState) => {
|
||||
// Make sure we're not sending a batch of closely repeated requests.
|
||||
// Make sure we"re not sending a batch of closely repeated requests.
|
||||
// This can easily happen whenever new sources are fetched.
|
||||
setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
|
||||
// In case there is still a request of listeners going on (it
|
||||
@ -81,7 +53,7 @@ const _getListeners = Task.async(function*() {
|
||||
const response = yield rdpInvoke(gThreadClient, gThreadClient.eventListeners);
|
||||
|
||||
// Make sure all the listeners are sorted by the event type, since
|
||||
// they're not guaranteed to be clustered together.
|
||||
// they"re not guaranteed to be clustered together.
|
||||
response.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
|
||||
|
||||
// Add all the listeners in the debugger view event linsteners container.
|
||||
@ -94,8 +66,8 @@ const _getListeners = Task.async(function*() {
|
||||
} else if (listener.function.class == "Function") {
|
||||
definitionSite = yield _getDefinitionSite(listener.function);
|
||||
if (!definitionSite) {
|
||||
// We don't know where this listener comes from so don't show it in
|
||||
// the UI as breaking on it doesn't work (bug 942899).
|
||||
// We don"t know where this listener comes from so don"t show it in
|
||||
// the UI as breaking on it doesn"t work (bug 942899).
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -141,7 +113,4 @@ function updateEventBreakpoints(eventNames) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
update: update,
|
||||
actions: { updateEventBreakpoints, fetchEventListeners }
|
||||
}
|
||||
module.exports = { updateEventBreakpoints, fetchEventListeners };
|
@ -0,0 +1,37 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const constants = require('../constants');
|
||||
|
||||
const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
|
||||
|
||||
const initialState = {
|
||||
activeEventNames: [],
|
||||
listeners: [],
|
||||
fetchingListeners: false,
|
||||
};
|
||||
|
||||
function update(state = initialState, action, emit) {
|
||||
switch(action.type) {
|
||||
case constants.UPDATE_EVENT_BREAKPOINTS:
|
||||
state.activeEventNames = action.eventNames;
|
||||
emit("@redux:activeEventNames", state.activeEventNames);
|
||||
break;
|
||||
case constants.FETCH_EVENT_LISTENERS:
|
||||
if (action.status === "begin") {
|
||||
state.fetchingListeners = true;
|
||||
}
|
||||
else if (action.status === "done") {
|
||||
state.fetchingListeners = false;
|
||||
state.listeners = action.listeners;
|
||||
emit("@redux:listeners", state.listeners);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
module.exports = update;
|
@ -5,4 +5,4 @@
|
||||
|
||||
const eventListeners = require('./event-listeners');
|
||||
|
||||
module.exports = { eventListeners };
|
||||
exports.eventListeners = eventListeners;
|
@ -3,26 +3,25 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const actions = require('../stores/event-listeners').actions;
|
||||
const bindActionCreators = require('devtools/shared/fluxify/bindActionCreators');
|
||||
const actions = require('../actions/event-listeners');
|
||||
const { bindActionCreators } = require('devtools/shared/vendor/redux');
|
||||
|
||||
/**
|
||||
* Functions handling the event listeners UI.
|
||||
*/
|
||||
function EventListenersView(dispatcher, DebuggerController) {
|
||||
function EventListenersView(store, DebuggerController) {
|
||||
dumpn("EventListenersView was instantiated");
|
||||
|
||||
this.actions = bindActionCreators(actions, dispatcher.dispatch);
|
||||
this.getState = () => dispatcher.getState().eventListeners;
|
||||
|
||||
this.Breakpoints = DebuggerController.Breakpoints;
|
||||
|
||||
dispatcher.onChange({
|
||||
"eventListeners": { "listeners": this.renderListeners }
|
||||
}, this);
|
||||
this.actions = bindActionCreators(actions, store.dispatch);
|
||||
this.getState = () => store.getState().eventListeners;
|
||||
|
||||
this._onCheck = this._onCheck.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onListeners = this._onListeners.bind(this);
|
||||
|
||||
this.Breakpoints = DebuggerController.Breakpoints;
|
||||
this.controller = DebuggerController;
|
||||
this.controller.on("@redux:listeners", this._onListeners);
|
||||
}
|
||||
|
||||
EventListenersView.prototype = Heritage.extend(WidgetMethods, {
|
||||
@ -53,8 +52,10 @@ EventListenersView.prototype = Heritage.extend(WidgetMethods, {
|
||||
destroy: function() {
|
||||
dumpn("Destroying the EventListenersView");
|
||||
|
||||
this.controller.off("@redux:listeners", this._onListeners);
|
||||
this.widget.removeEventListener("check", this._onCheck, false);
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
this.controller = this.Breakpoints = null;
|
||||
},
|
||||
|
||||
renderListeners: function(listeners) {
|
||||
@ -285,6 +286,13 @@ EventListenersView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when listeners change.
|
||||
*/
|
||||
_onListeners: function(_, listeners) {
|
||||
this.renderListeners(listeners);
|
||||
},
|
||||
|
||||
_eventCheckboxTooltip: "",
|
||||
_onSelectorString: "",
|
||||
_inSourceString: "",
|
||||
|
@ -1280,7 +1280,7 @@ SourceScripts.prototype = {
|
||||
|
||||
// Make sure the events listeners are up to date.
|
||||
if (DebuggerView.instrumentsPaneTab == "events-tab") {
|
||||
dispatcher.dispatch(actions.fetchEventListeners());
|
||||
store.dispatch(actions.fetchEventListeners());
|
||||
}
|
||||
|
||||
// Signal that a new source has been added.
|
||||
@ -2055,6 +2055,7 @@ var Prefs = new ViewHelpers.Prefs("devtools", {
|
||||
* Convenient way of emitting events from the panel window.
|
||||
*/
|
||||
EventEmitter.decorate(this);
|
||||
EventEmitter.decorate(DebuggerController);
|
||||
|
||||
/**
|
||||
* Preliminary setup for the DebuggerController object.
|
||||
|
@ -35,16 +35,19 @@ const RESIZE_REFRESH_RATE = 50; // ms
|
||||
const PROMISE_DEBUGGER_URL =
|
||||
"chrome://browser/content/devtools/promisedebugger/promise-debugger.xhtml";
|
||||
|
||||
const createDispatcher = require('devtools/shared/create-dispatcher')();
|
||||
const stores = require('./content/stores/index');
|
||||
const dispatcher = createDispatcher(stores);
|
||||
const waitUntilService = require('devtools/shared/fluxify/waitUntilService');
|
||||
const debuggerControllerEmit = DebuggerController.emit.bind(DebuggerController);
|
||||
const createStore = require("devtools/shared/redux/create-store")();
|
||||
const { combineEmittingReducers } = require("devtools/shared/redux/reducers");
|
||||
const reducers = require("./content/reducers/index");
|
||||
const store = createStore(combineEmittingReducers(reducers, debuggerControllerEmit));
|
||||
const { NAME: WAIT_UNTIL_NAME } = require("devtools/shared/redux/middleware/wait-service");
|
||||
|
||||
const services = {
|
||||
WAIT_UNTIL: waitUntilService.name
|
||||
WAIT_UNTIL: WAIT_UNTIL_NAME
|
||||
};
|
||||
|
||||
const EventListenersView = require('./content/views/event-listeners-view');
|
||||
const actions = require('./content/stores/event-listeners').actions;
|
||||
const actions = require('./content/actions/event-listeners');
|
||||
|
||||
/**
|
||||
* Object defining the debugger view components.
|
||||
@ -626,7 +629,7 @@ var DebuggerView = {
|
||||
*/
|
||||
_onInstrumentsPaneTabSelect: function() {
|
||||
if (this._instrumentsPane.selectedTab.id == "events-tab") {
|
||||
dispatcher.dispatch(actions.fetchEventListeners());
|
||||
store.dispatch(actions.fetchEventListeners());
|
||||
}
|
||||
},
|
||||
|
||||
@ -928,4 +931,4 @@ ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
|
||||
top: 0
|
||||
});
|
||||
|
||||
DebuggerView.EventListeners = new EventListenersView(dispatcher, DebuggerController);
|
||||
DebuggerView.EventListeners = new EventListenersView(store, DebuggerController);
|
||||
|
@ -18,9 +18,13 @@ EXTRA_JS_MODULES.devtools.debugger.content.views += [
|
||||
'content/views/event-listeners-view.js'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.debugger.content.stores += [
|
||||
'content/stores/event-listeners.js',
|
||||
'content/stores/index.js'
|
||||
EXTRA_JS_MODULES.devtools.debugger.content.reducers += [
|
||||
'content/reducers/event-listeners.js',
|
||||
'content/reducers/index.js'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.debugger.content.actions += [
|
||||
'content/actions/event-listeners.js',
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/mochitest/browser.ini']
|
||||
|
@ -13,7 +13,7 @@ function test() {
|
||||
let gDebugger = aPanel.panelWin;
|
||||
let gView = gDebugger.DebuggerView;
|
||||
let gEvents = gView.EventListeners;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let gStore = gDebugger.store;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
@ -26,7 +26,7 @@ function test() {
|
||||
|
||||
function testFetchOnFocus() {
|
||||
return Task.spawn(function*() {
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
is(gView.instrumentsPaneHidden, false,
|
||||
@ -45,7 +45,7 @@ function test() {
|
||||
|
||||
function testFetchOnReloadWhenFocused() {
|
||||
return Task.spawn(function*() {
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
|
||||
let reloading = once(gDebugger.gTarget, "will-navigate");
|
||||
let reloaded = waitForSourcesAfterReload();
|
||||
@ -76,7 +76,7 @@ function test() {
|
||||
|
||||
function testFetchOnReloadWhenNotFocused() {
|
||||
return Task.spawn(function*() {
|
||||
gDispatcher.dispatch({
|
||||
gStore.dispatch({
|
||||
type: gDebugger.services.WAIT_UNTIL,
|
||||
predicate: action => {
|
||||
return (action.type === constants.FETCH_EVENT_LISTENERS ||
|
||||
|
@ -12,13 +12,13 @@ function test() {
|
||||
let gDebugger = aPanel.panelWin;
|
||||
let gView = gDebugger.DebuggerView;
|
||||
let gEvents = gView.EventListeners;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let gStore = gDebugger.store;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
yield waitForSourceShown(aPanel, ".html");
|
||||
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
yield fetched;
|
||||
|
||||
|
@ -14,14 +14,14 @@ function test() {
|
||||
let gView = gDebugger.DebuggerView;
|
||||
let gController = gDebugger.DebuggerController
|
||||
let gEvents = gView.EventListeners;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let getState = gDispatcher.getState;
|
||||
let gStore = gDebugger.store;
|
||||
let getState = gStore.getState;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
yield waitForSourceShown(aPanel, ".html");
|
||||
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
yield fetched;
|
||||
|
||||
@ -34,7 +34,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "");
|
||||
|
||||
let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
|
||||
yield updated;
|
||||
|
||||
@ -47,7 +47,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "change");
|
||||
|
||||
updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
|
||||
yield updated;
|
||||
|
||||
|
@ -15,14 +15,14 @@ function test() {
|
||||
let gView = gDebugger.DebuggerView;
|
||||
let gController = gDebugger.DebuggerController
|
||||
let gEvents = gView.EventListeners;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let getState = gDispatcher.getState;
|
||||
let gStore = gDebugger.store;
|
||||
let getState = gStore.getState;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
yield waitForSourceShown(aPanel, ".html");
|
||||
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
yield fetched;
|
||||
|
||||
@ -35,7 +35,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "");
|
||||
|
||||
let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
|
||||
yield updated;
|
||||
|
||||
@ -48,7 +48,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "change");
|
||||
|
||||
updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
|
||||
yield updated;
|
||||
|
||||
@ -61,7 +61,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "");
|
||||
|
||||
updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
|
||||
yield updated;
|
||||
|
||||
@ -74,7 +74,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "keydown,keyup");
|
||||
|
||||
updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
|
||||
yield updated;
|
||||
|
||||
|
@ -15,14 +15,14 @@ function test() {
|
||||
let gController = gDebugger.DebuggerController
|
||||
let gEvents = gView.EventListeners;
|
||||
let gBreakpoints = gController.Breakpoints;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let getState = gDispatcher.getState;
|
||||
let gStore = gDebugger.store;
|
||||
let getState = gStore.getState;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
yield waitForSourceShown(aPanel, ".html");
|
||||
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
yield fetched;
|
||||
|
||||
@ -35,7 +35,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "");
|
||||
|
||||
let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
|
||||
@ -51,7 +51,7 @@ function test() {
|
||||
testEventArrays("change,click,keydown,keyup", "change,click,keydown");
|
||||
|
||||
reload(aPanel);
|
||||
yield afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
|
||||
testEventItem(0, true);
|
||||
testEventItem(1, true);
|
||||
@ -62,7 +62,7 @@ function test() {
|
||||
testEventGroup("mouseEvents", false);
|
||||
testEventArrays("change,click,keydown,keyup", "change,click,keydown");
|
||||
|
||||
updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
|
||||
@ -78,7 +78,7 @@ function test() {
|
||||
testEventArrays("change,click,keydown,keyup", "");
|
||||
|
||||
reload(aPanel);
|
||||
yield afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
yield afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
|
||||
testEventItem(0, false);
|
||||
testEventItem(1, false);
|
||||
|
@ -13,15 +13,15 @@ function test() {
|
||||
let gDebugger = aPanel.panelWin;
|
||||
let gView = gDebugger.DebuggerView;
|
||||
let gEvents = gView.EventListeners;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let getState = gDispatcher.getState;
|
||||
let gStore = gDebugger.store;
|
||||
let getState = gStore.getState;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
|
||||
Task.spawn(function*() {
|
||||
yield waitForSourceShown(aPanel, ".html");
|
||||
yield callInTab(gTab, "addBodyClickEventListener");
|
||||
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
|
||||
yield fetched;
|
||||
yield ensureThreadClientState(aPanel, "attached");
|
||||
@ -31,7 +31,7 @@ function test() {
|
||||
is(gView.instrumentsPaneTab, "events-tab",
|
||||
"The events tab should be selected.");
|
||||
|
||||
let updated = afterDispatch(gDispatcher, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
let updated = afterDispatch(gStore, constants.UPDATE_EVENT_BREAKPOINTS);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
|
||||
yield updated;
|
||||
yield ensureThreadClientState(aPanel, "attached");
|
||||
|
@ -31,13 +31,13 @@ add_task(function* () {
|
||||
|
||||
let [,, panel, win] = yield initDebugger(tab);
|
||||
let gDebugger = panel.panelWin;
|
||||
let gDispatcher = gDebugger.dispatcher;
|
||||
let gStore = gDebugger.store;
|
||||
let constants = gDebugger.require('./content/constants');
|
||||
let eventListeners = gDebugger.require('./content/stores/event-listeners');
|
||||
let fetched = afterDispatch(gDispatcher, constants.FETCH_EVENT_LISTENERS);
|
||||
let actions = gDebugger.require('./content/actions/event-listeners');
|
||||
let fetched = afterDispatch(gStore, constants.FETCH_EVENT_LISTENERS);
|
||||
|
||||
info("Scheduling event listener fetch.");
|
||||
gDispatcher.dispatch(eventListeners.actions.fetchEventListeners());
|
||||
gStore.dispatch(actions.fetchEventListeners());
|
||||
|
||||
info("Waiting for updated event listeners to arrive.");
|
||||
yield fetched;
|
||||
|
@ -1192,10 +1192,10 @@ function source(sourceClient) {
|
||||
return rdpInvoke(sourceClient, sourceClient.source);
|
||||
}
|
||||
|
||||
function afterDispatch(dispatcher, type) {
|
||||
function afterDispatch(store, type) {
|
||||
info("Waiting on dispatch: " + type);
|
||||
return new Promise(resolve => {
|
||||
dispatcher.dispatch({
|
||||
store.dispatch({
|
||||
// Normally we would use `services.WAIT_UNTIL`, but use the
|
||||
// internal name here so tests aren't forced to always pass it
|
||||
// in
|
||||
|
@ -3,6 +3,8 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
browser.jar:
|
||||
content/browser/devtools/d3.js (shared/vendor/d3.js)
|
||||
content/browser/devtools/dagre-d3.js (shared/vendor/dagre-d3.js)
|
||||
content/browser/devtools/widgets.css (shared/widgets/widgets.css)
|
||||
content/browser/devtools/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
|
||||
content/browser/devtools/markup-view.xhtml (markupview/markup-view.xhtml)
|
||||
@ -84,9 +86,7 @@ browser.jar:
|
||||
content/browser/devtools/canvasdebugger.js (canvasdebugger/canvasdebugger.js)
|
||||
content/browser/devtools/canvasdebugger/snapshotslist.js (canvasdebugger/snapshotslist.js)
|
||||
content/browser/devtools/canvasdebugger/callslist.js (canvasdebugger/callslist.js)
|
||||
content/browser/devtools/d3.js (shared/d3.js)
|
||||
content/browser/devtools/webaudioeditor.xul (webaudioeditor/webaudioeditor.xul)
|
||||
content/browser/devtools/dagre-d3.js (webaudioeditor/lib/dagre-d3.js)
|
||||
content/browser/devtools/webaudioeditor/includes.js (webaudioeditor/includes.js)
|
||||
content/browser/devtools/webaudioeditor/models.js (webaudioeditor/models.js)
|
||||
content/browser/devtools/webaudioeditor/controller.js (webaudioeditor/controller.js)
|
||||
|
@ -23,6 +23,7 @@ catch(e) {
|
||||
};
|
||||
}
|
||||
|
||||
const VENDOR_CONTENT_URL = "resource:///modules/devtools/shared/vendor";
|
||||
|
||||
/*
|
||||
* Create a loader to be used in a browser environment. This evaluates
|
||||
@ -55,8 +56,8 @@ function BrowserLoader(baseURI, window) {
|
||||
let dynamicPaths = {};
|
||||
if (appConstants.DEBUG_JS_MODULES) {
|
||||
// Load in the dev version of React
|
||||
dynamicPaths["devtools/shared/content/react"] =
|
||||
"resource:///modules/devtools/shared/content/react-dev.js";
|
||||
dynamicPaths["devtools/shared/vendor/react"] =
|
||||
"resource:///modules/devtools/vendor/react-dev.js";
|
||||
}
|
||||
|
||||
const opts = {
|
||||
@ -67,8 +68,9 @@ function BrowserLoader(baseURI, window) {
|
||||
invisibleToDebugger: loaderOptions.invisibleToDebugger,
|
||||
require: (id, require) => {
|
||||
const uri = require.resolve(id);
|
||||
|
||||
if (!uri.startsWith(baseURI) &&
|
||||
!uri.startsWith("resource:///modules/devtools/shared/content")) {
|
||||
!uri.startsWith(VENDOR_CONTENT_URL)) {
|
||||
return devtools.require(uri);
|
||||
}
|
||||
return require(uri);
|
||||
|
@ -1,28 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
function bindActionCreator(actionCreator, dispatch) {
|
||||
return (...args) => dispatch(actionCreator(...args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps action creator functions into a function that automatically
|
||||
* dispatches the created action with `dispatch`. Normally action
|
||||
* creators simply return actions, but wrapped functions will
|
||||
* automatically dispatch.
|
||||
*
|
||||
* @param {object} actionCreators
|
||||
* An object of action creator functions (names as keys)
|
||||
* @param {function} dispatch
|
||||
*/
|
||||
function bindActionCreators(actionCreators, dispatch) {
|
||||
let actions = {};
|
||||
for (let k of Object.keys(actionCreators)) {
|
||||
actions[k] = bindActionCreator(actionCreators[k], dispatch);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
module.exports = bindActionCreators;
|
@ -1,247 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { entries, compose } = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* A store creator that creates a dispatch function that runs the
|
||||
* provided middlewares before actually dispatching. This allows
|
||||
* simple middleware to augment the kinds of actions that can
|
||||
* be dispatched. This would be used like this:
|
||||
* `createDispatcher = applyMiddleware([fooMiddleware, ...])(createDispatcher)`
|
||||
*
|
||||
* Middlewares are simple functions that are provided `dispatch` and
|
||||
* `getState` functions. They create functions that accept actions and
|
||||
* can re-dispatch them in any way they want. A common scenario is
|
||||
* asynchronously dispatching multiple actions. Here is a full
|
||||
* middleware:
|
||||
*
|
||||
* function thunkMiddleware({ dispatch, getState }) {
|
||||
* return next => action => {
|
||||
* return typeof action === 'function' ?
|
||||
* action(dispatch, getState) :
|
||||
* next(action);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* `next` is essentially a `dispatch` function, but it calls the next
|
||||
* middelware in the chain (or the real `dispatch` function). Using
|
||||
* this middleware, you can return a function that gives you a
|
||||
* dispatch function:
|
||||
*
|
||||
* function actionCreator(timeout) {
|
||||
* return (dispatch, getState) => {
|
||||
* dispatch({ type: TIMEOUT, status: "start" });
|
||||
* setTimeout(() => dispatch({ type: TIMEOUT, status: "end" }),
|
||||
* timeout);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
function applyMiddleware(...middlewares) {
|
||||
return next => stores => {
|
||||
const dispatcher = next(stores);
|
||||
let dispatch = dispatcher.dispatch;
|
||||
|
||||
const api = {
|
||||
getState: dispatcher.getState,
|
||||
dispatch: action => dispatch(action)
|
||||
};
|
||||
const chain = middlewares.map(middleware => middleware(api));
|
||||
dispatch = compose(...chain)(dispatcher.dispatch);
|
||||
|
||||
return Object.assign({}, dispatcher, { dispatch: dispatch });
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The heart of the system. This creates a dispatcher which is the
|
||||
* interface between views and stores. Views can use a dispatcher
|
||||
* instance to dispatch actions, which know nothing about the stores.
|
||||
* Actions are broadcasted to all registered stores, and stores can
|
||||
* handle the action and update their state. The dispatcher gives
|
||||
* stores an `emitChange` function, which signifies that a piece of
|
||||
* state has changed. The dispatcher will notify all views that are
|
||||
* listening to that piece of state, registered with `onChange`.
|
||||
*
|
||||
* Views generally are stateless, pure components (eventually React
|
||||
* components). They simply take state and render it.
|
||||
*
|
||||
* Stores make up the entire app state, and are all grouped together
|
||||
* into a single app state atom, returned by the dispatcher's
|
||||
* `getState` function. The shape of the app state is determined by
|
||||
* the `stores` object passed in to the dispatcher, so if
|
||||
* `{ foo: fooStore }` was passed to `createDispatcher` the app state
|
||||
* would be `{ foo: fooState }`
|
||||
*
|
||||
* Actions are just JavaScript object with a `type` property and any
|
||||
* other fields pertinent to the action. Action creators should
|
||||
* generally be used to create actions, which are just functions that
|
||||
* return the action object. Additionally, the `bindActionCreators`
|
||||
* module provides a function for automatically binding action
|
||||
* creators to a dispatch function, so calling them automatically
|
||||
* dispatches. For example:
|
||||
*
|
||||
* ```js
|
||||
* // Manually dispatch
|
||||
* dispatcher.dispatch({ type: constants.ADD_ITEM, item: item });
|
||||
* // Using an action creator
|
||||
* dispatcher.dispatch(addItem(item));
|
||||
*
|
||||
* // Using an action creator bound to dispatch
|
||||
* actions = bindActionCreators({ addItem: addItem });
|
||||
* actions.addItem(item);
|
||||
* ```
|
||||
*
|
||||
* Our system expects stores to exist as an `update` function. You
|
||||
* should define an update function in a module, and optionally
|
||||
* any action creators that are useful to go along with it. Here is
|
||||
* an example store file:
|
||||
*
|
||||
* ```js
|
||||
* const initialState = { items: [] };
|
||||
* function update(state = initialState, action, emitChange) {
|
||||
* if (action.type === constants.ADD_ITEM) {
|
||||
* state.items.push(action.item);
|
||||
* emitChange("items", state.items);
|
||||
* }
|
||||
*
|
||||
* return state;
|
||||
* }
|
||||
*
|
||||
* function addItem(item) {
|
||||
* return {
|
||||
* type: constants.ADD_ITEM,
|
||||
* item: item
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* module.exports = {
|
||||
* update: update,
|
||||
* actions: { addItem }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Lastly, "constants" are simple strings that specify action names.
|
||||
* Usually the entire set of available action types are specified in
|
||||
* a `constants.js` file, so they are available globally. Use
|
||||
* variables that are the same name as the string, for example
|
||||
* `const ADD_ITEM = "ADD_ITEM"`.
|
||||
*
|
||||
* This entire system was inspired by Redux, which hopefully we will
|
||||
* eventually use once things get cleaned up enough. You should read
|
||||
* its docs, and keep in mind that it calls stores "reducers" and the
|
||||
* dispatcher instance is called a single store.
|
||||
* http://rackt.github.io/redux/
|
||||
*/
|
||||
function createDispatcher(stores) {
|
||||
const state = {};
|
||||
const listeners = {};
|
||||
let enqueuedChanges = [];
|
||||
let isDispatching = false;
|
||||
|
||||
// Validate the stores to make sure they have the right shape,
|
||||
// and accumulate the initial state
|
||||
entries(stores).forEach(([name, store]) => {
|
||||
if (!store || typeof store.update !== "function") {
|
||||
throw new Error("Error creating dispatcher: store \"" + name +
|
||||
"\" does not have an `update` function");
|
||||
}
|
||||
|
||||
state[name] = store.update(undefined, {});
|
||||
});
|
||||
|
||||
function getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
function emitChange(storeName, dataName, payload) {
|
||||
enqueuedChanges.push([storeName, dataName, payload]);
|
||||
}
|
||||
|
||||
function onChange(paths, view) {
|
||||
entries(paths).forEach(([storeName, data]) => {
|
||||
if (!stores[storeName]) {
|
||||
throw new Error("Error adding onChange handler to store: store " +
|
||||
"\"" + storeName + "\" does not exist");
|
||||
}
|
||||
|
||||
if (!listeners[storeName]) {
|
||||
listeners[storeName] = [];
|
||||
}
|
||||
|
||||
if (typeof data == 'function') {
|
||||
listeners[storeName].push(data.bind(view));
|
||||
}
|
||||
else {
|
||||
entries(data).forEach(([watchedName, handler]) => {
|
||||
listeners[storeName].push((payload, dataName, storeName) => {
|
||||
if (dataName === watchedName) {
|
||||
handler.call(view, payload, dataName, storeName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any enqueued state changes from the dispatch cycle. Listeners
|
||||
* are not immediately notified of changes, only after dispatching
|
||||
* is completed, to ensure that all state is consistent (in the case
|
||||
* of multiple stores changes at once).
|
||||
*/
|
||||
function flushChanges() {
|
||||
enqueuedChanges.forEach(([storeName, dataName, payload]) => {
|
||||
if (listeners[storeName]) {
|
||||
listeners[storeName].forEach(listener => {
|
||||
listener(payload, dataName, storeName);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
enqueuedChanges = [];
|
||||
}
|
||||
|
||||
function dispatch(action) {
|
||||
if (isDispatching) {
|
||||
throw new Error('Cannot dispatch in the middle of a dispatch');
|
||||
}
|
||||
if (!action.type) {
|
||||
throw new Error(
|
||||
'action type is null, ' +
|
||||
'did you make a typo when publishing this action? ' +
|
||||
JSON.stringify(action, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
isDispatching = true;
|
||||
try {
|
||||
entries(stores).forEach(([name, store]) => {
|
||||
state[name] = store.update(
|
||||
state[name],
|
||||
action,
|
||||
emitChange.bind(null, name)
|
||||
);
|
||||
});
|
||||
}
|
||||
finally {
|
||||
isDispatching = false;
|
||||
}
|
||||
|
||||
flushChanges();
|
||||
}
|
||||
|
||||
return {
|
||||
getState,
|
||||
dispatch,
|
||||
onChange
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDispatcher: createDispatcher,
|
||||
applyMiddleware: applyMiddleware
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const createDispatcher = require('devtools/shared/create-dispatcher')({ log: true });
|
||||
const waitUntilService = require('devtools/shared/fluxify/waitUntilService');
|
||||
const services = {
|
||||
WAIT_UNTIL: waitUntilService.name
|
||||
};
|
||||
|
||||
const Services = require("Services");
|
||||
const { waitForTick, waitForTime } = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
var loadSubScript = Cc[
|
||||
'@mozilla.org/moz/jssubscript-loader;1'
|
||||
].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
|
||||
|
||||
function getFileUrl(name, allowMissing=false) {
|
||||
let file = do_get_file(name, allowMissing);
|
||||
return Services.io.newFileURI(file).spec;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This file should be loaded with `loadSubScript` because it contains
|
||||
// stateful stores that need a new instance per test.
|
||||
|
||||
const NumberStore = {
|
||||
update: (state = 1, action, emitChange) => {
|
||||
switch(action.type) {
|
||||
case constants.ADD_NUMBER: {
|
||||
const newState = state + action.value;
|
||||
emitChange('number', newState);
|
||||
return newState;
|
||||
}
|
||||
case constants.DOUBLE_NUMBER: {
|
||||
const newState = state * 2;
|
||||
emitChange('number', newState);
|
||||
return newState;
|
||||
}}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
constants: {
|
||||
ADD_NUMBER: 'ADD_NUMBER',
|
||||
DOUBLE_NUMBER: 'DOUBLE_NUMBER'
|
||||
}
|
||||
};
|
||||
|
||||
const itemsInitialState = {
|
||||
list: []
|
||||
};
|
||||
const ItemStore = {
|
||||
update: (state = itemsInitialState, action, emitChange) => {
|
||||
switch(action.type) {
|
||||
case constants.ADD_ITEM:
|
||||
state.list.push(action.item);
|
||||
emitChange('list', state.list);
|
||||
return state;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
constants: {
|
||||
ADD_ITEM: 'ADD_ITEM'
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
loadSubScript(getFileUrl("stores-for-testing.js"));
|
||||
const constants = Object.assign(
|
||||
{},
|
||||
NumberStore.constants,
|
||||
ItemStore.constants
|
||||
);
|
||||
const stores = { number: NumberStore,
|
||||
items: ItemStore };
|
||||
|
||||
function addNumber(num) {
|
||||
return {
|
||||
type: constants.ADD_NUMBER,
|
||||
value: num
|
||||
}
|
||||
}
|
||||
|
||||
function addItem(item) {
|
||||
return {
|
||||
type: constants.ADD_ITEM,
|
||||
item: item
|
||||
};
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
function run_test() {
|
||||
testInitialValue();
|
||||
testDispatch();
|
||||
testEmitChange();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function testInitialValue() {
|
||||
do_print("Testing initial value");
|
||||
const dispatcher = createDispatcher(stores);
|
||||
equal(dispatcher.getState().number, 1);
|
||||
}
|
||||
|
||||
function testDispatch() {
|
||||
do_print("Testing dispatch");
|
||||
|
||||
const dispatcher = createDispatcher(stores);
|
||||
dispatcher.dispatch(addNumber(5));
|
||||
equal(dispatcher.getState().number, 6);
|
||||
|
||||
dispatcher.dispatch(addNumber(2));
|
||||
equal(dispatcher.getState().number, 8);
|
||||
|
||||
// It should ignore unknown action types
|
||||
dispatcher.dispatch({ type: "FOO" });
|
||||
equal(dispatcher.getState().number, 8);
|
||||
}
|
||||
|
||||
function testEmitChange() {
|
||||
do_print("Testing change emittters");
|
||||
const dispatcher = createDispatcher(stores);
|
||||
let listenerRan = false;
|
||||
|
||||
const numberView = {
|
||||
x: 3,
|
||||
renderNumber: function(num) {
|
||||
ok(this.x, 3, "listener ran in context of view");
|
||||
ok(num, 10);
|
||||
listenerRan = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Views can listen to changes in state by specifying which part of
|
||||
// the state to listen to and giving a handler function. The
|
||||
// function will be run with the view as `this`.
|
||||
dispatcher.onChange({
|
||||
"number": numberView.renderNumber
|
||||
}, numberView);
|
||||
|
||||
dispatcher.dispatch(addNumber(9));
|
||||
ok(listenerRan, "number listener actually ran");
|
||||
listenerRan = false;
|
||||
|
||||
const itemsView = {
|
||||
renderList: function(items) {
|
||||
ok(items.length, 1);
|
||||
ok(items[0].name = "james");
|
||||
listenerRan = true;
|
||||
}
|
||||
}
|
||||
|
||||
// You can listen to deeper sections of the state by nesting objects
|
||||
// to specify the path to that state. You can do this 1 level deep;
|
||||
// you cannot arbitrarily nest state listeners.
|
||||
dispatcher.onChange({
|
||||
"items": {
|
||||
"list": itemsView.renderList
|
||||
}
|
||||
}, itemsView);
|
||||
|
||||
dispatcher.dispatch(addItem({ name: "james" }));
|
||||
ok(listenerRan, "items listener actually ran");
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
loadSubScript(getFileUrl('stores-for-testing.js'));
|
||||
const constants = NumberStore.constants;
|
||||
const stores = { number: NumberStore };
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* testThunkDispatch() {
|
||||
do_print("Testing thunk dispatch");
|
||||
// The thunk middleware allows you to return a function from an
|
||||
// action creator which takes `dispatch` and `getState` functions as
|
||||
// arguments. This allows the action creator to fire multiple
|
||||
// actions manually with `dispatch`, possibly asynchronously.
|
||||
|
||||
function addNumberLater(num) {
|
||||
return dispatch => {
|
||||
// Just do it in the next tick, no need to wait too long
|
||||
waitForTick().then(() => {
|
||||
dispatch({
|
||||
type: constants.ADD_NUMBER,
|
||||
value: num
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function addNumber(num) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: constants.ADD_NUMBER,
|
||||
value: getState().number > 10 ? (num * 2) : num
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const dispatcher = createDispatcher(stores);
|
||||
equal(dispatcher.getState().number, 1);
|
||||
dispatcher.dispatch(addNumberLater(5));
|
||||
equal(dispatcher.getState().number, 1, "state should not have changed");
|
||||
yield waitForTick();
|
||||
equal(dispatcher.getState().number, 6, "state should have changed");
|
||||
|
||||
dispatcher.dispatch(addNumber(5));
|
||||
equal(dispatcher.getState().number, 11);
|
||||
dispatcher.dispatch(addNumber(2));
|
||||
// 2 * 2 should have actually been added because the state is
|
||||
// greater than 10 (the action creator changes the value based on
|
||||
// the current state)
|
||||
equal(dispatcher.getState().number, 15);
|
||||
});
|
||||
|
||||
add_task(function* testWaitUntilService() {
|
||||
do_print("Testing waitUntil service");
|
||||
// The waitUntil service allows you to queue functions to be run at a
|
||||
// later time, depending on a predicate. As actions comes through
|
||||
// the system, you predicate will be called with each action. Once
|
||||
// your predicate returns true, the queued function will be run and
|
||||
// removed from the pending queue.
|
||||
|
||||
function addWhenDoubled(num) {
|
||||
return {
|
||||
type: services.WAIT_UNTIL,
|
||||
predicate: action => action.type === constants.DOUBLE_NUMBER,
|
||||
run: (dispatch, getState, action) => {
|
||||
ok(action.type, constants.DOUBLE_NUMBER);
|
||||
ok(getState(), 10);
|
||||
|
||||
dispatch({
|
||||
type: constants.ADD_NUMBER,
|
||||
value: 2
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addWhenGreaterThan(threshold, num) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: services.WAIT_UNTIL,
|
||||
predicate: () => getState().number > threshold,
|
||||
run: () => {
|
||||
dispatch({
|
||||
type: constants.ADD_NUMBER,
|
||||
value: num
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const dispatcher = createDispatcher(stores);
|
||||
|
||||
// Add a pending action that adds 2 after the number is doubled
|
||||
equal(dispatcher.getState().number, 1);
|
||||
dispatcher.dispatch(addWhenDoubled(2));
|
||||
equal(dispatcher.getState().number, 1);
|
||||
dispatcher.dispatch({ type: constants.DOUBLE_NUMBER });
|
||||
// Note how the pending function we added ran synchronously. It
|
||||
// should have added 2 after doubling 1, so 1 * 2 + 2 = 4
|
||||
equal(dispatcher.getState().number, 4);
|
||||
|
||||
// Add a pending action that adds 5 once the number is greater than 10
|
||||
dispatcher.dispatch(addWhenGreaterThan(10, 5));
|
||||
equal(dispatcher.getState().number, 4);
|
||||
dispatcher.dispatch({ type: constants.ADD_NUMBER, value: 10 });
|
||||
// Again, the pending function we added ran synchronously. It should
|
||||
// have added 5 more after 10 was added, since the number was
|
||||
// greater than 10.
|
||||
equal(dispatcher.getState().number, 19);
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
support-files =
|
||||
stores-for-testing.js
|
||||
|
||||
[test_dispatcher.js]
|
||||
[test_middlewares.js]
|
@ -7,6 +7,11 @@
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
||||
DIRS += [
|
||||
'redux',
|
||||
'vendor',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools += [
|
||||
'AppCacheUtils.jsm',
|
||||
'Curl.jsm',
|
||||
@ -32,7 +37,6 @@ EXTRA_JS_MODULES.devtools += [
|
||||
EXTRA_JS_MODULES.devtools.shared += [
|
||||
'autocomplete-popup.js',
|
||||
'browser-loader.js',
|
||||
'create-dispatcher.js',
|
||||
'devices.js',
|
||||
'doorhanger.js',
|
||||
'frame-script-utils.js',
|
||||
@ -49,15 +53,6 @@ EXTRA_JS_MODULES.devtools.shared += [
|
||||
'undo.js'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.content += [
|
||||
'content/react.js'
|
||||
]
|
||||
|
||||
if CONFIG['DEBUG_JS_MODULES']:
|
||||
EXTRA_JS_MODULES.devtools.shared.content += [
|
||||
'content/react-dev.js'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.widgets += [
|
||||
'widgets/BarGraphWidget.js',
|
||||
'widgets/CubicBezierPresets.js',
|
||||
@ -74,5 +69,3 @@ EXTRA_JS_MODULES.devtools.shared.widgets += [
|
||||
'widgets/Tooltip.js',
|
||||
'widgets/TreeWidget.js',
|
||||
]
|
||||
|
||||
DIRS += ['fluxify']
|
||||
|
@ -3,11 +3,10 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const fluxify = require('./fluxify/dispatcher');
|
||||
const thunkMiddleware = require('./fluxify/thunkMiddleware');
|
||||
const logMiddleware = require('./fluxify/logMiddleware');
|
||||
const waitUntilService = require('./fluxify/waitUntilService')
|
||||
const { compose } = require('devtools/toolkit/DevToolsUtils');
|
||||
const { createStore, applyMiddleware } = require("devtools/shared/vendor/redux");
|
||||
const { thunk } = require("./middleware/thunk");
|
||||
const { waitUntilService } = require("./middleware/wait-service");
|
||||
const { log } = require("./middleware/log");
|
||||
|
||||
/**
|
||||
* This creates a dispatcher with all the standard middleware in place
|
||||
@ -16,16 +15,21 @@ const { compose } = require('devtools/toolkit/DevToolsUtils');
|
||||
*
|
||||
* @param {object} opts - boolean configuration flags
|
||||
* - log: log all dispatched actions to console
|
||||
* - middleware: array of middleware to be included in the redux store
|
||||
*/
|
||||
module.exports = (opts={}) => {
|
||||
const middleware = [
|
||||
thunkMiddleware,
|
||||
waitUntilService.service
|
||||
thunk,
|
||||
waitUntilService
|
||||
];
|
||||
|
||||
if (opts.log) {
|
||||
middleware.push(logMiddleware);
|
||||
middleware.push(log);
|
||||
}
|
||||
|
||||
return fluxify.applyMiddleware(...middleware)(fluxify.createDispatcher);
|
||||
if (opts.middleware) {
|
||||
opts.middleware.forEach(fn => middleware.push(fn));
|
||||
}
|
||||
|
||||
return applyMiddleware(...middleware)(createStore);
|
||||
}
|
@ -7,11 +7,11 @@
|
||||
* A middleware that logs all actions coming through the system
|
||||
* to the console.
|
||||
*/
|
||||
function logMiddleware({ dispatch, getState }) {
|
||||
function log({ dispatch, getState }) {
|
||||
return next => action => {
|
||||
console.log('[DISPATCH]', JSON.stringify(action));
|
||||
console.log("[DISPATCH]", JSON.stringify(action));
|
||||
next(action);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = logMiddleware;
|
||||
exports.log = log;
|
@ -9,12 +9,11 @@
|
||||
* allowing the action to create multiple actions (most likely
|
||||
* asynchronously).
|
||||
*/
|
||||
function thunkMiddleware({ dispatch, getState }) {
|
||||
function thunk({ dispatch, getState }) {
|
||||
return next => action => {
|
||||
return typeof action === "function"
|
||||
? action(dispatch, getState)
|
||||
: next(action);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = thunkMiddleware;
|
||||
exports.thunk = thunk;
|
@ -3,8 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const NAME = "@@service/waitUntil";
|
||||
|
||||
/**
|
||||
* A middleware which acts like a service, because it is stateful
|
||||
* and "long-running" in the background. It provides the ability
|
||||
@ -13,7 +11,7 @@ const NAME = "@@service/waitUntil";
|
||||
* it as a thunk that blocks until the condition is met. Example:
|
||||
*
|
||||
* ```js
|
||||
* const services = { WAIT_UNTIL: require('waitUntilService').name };
|
||||
* const services = { WAIT_UNTIL: require('wait-service').NAME };
|
||||
*
|
||||
* { type: services.WAIT_UNTIL,
|
||||
* predicate: action => action.type === constants.ADD_ITEM,
|
||||
@ -25,6 +23,8 @@ const NAME = "@@service/waitUntil";
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const NAME = exports.NAME = "@@service/waitUntil";
|
||||
|
||||
function waitUntilService({ dispatch, getState }) {
|
||||
let pending = [];
|
||||
|
||||
@ -62,8 +62,4 @@ function waitUntilService({ dispatch, getState }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
service: waitUntilService,
|
||||
name: NAME
|
||||
};
|
||||
exports.waitUntilService = waitUntilService;
|
16
browser/devtools/shared/redux/moz.build
Normal file
16
browser/devtools/shared/redux/moz.build
Normal file
@ -0,0 +1,16 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.redux += [
|
||||
'create-store.js',
|
||||
'reducers.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.redux.middleware += [
|
||||
'middleware/log.js',
|
||||
'middleware/thunk.js',
|
||||
'middleware/wait-service.js',
|
||||
]
|
33
browser/devtools/shared/redux/reducers.js
Normal file
33
browser/devtools/shared/redux/reducers.js
Normal file
@ -0,0 +1,33 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { combineReducers } = require("devtools/shared/vendor/redux");
|
||||
|
||||
/**
|
||||
* Function that takes a hash of reducers, like `combineReducers`,
|
||||
* and an `emit` function and returns a function to be used as a reducer
|
||||
* for a Redux store. This allows all reducers defined here to receive
|
||||
* a third argument, the `emit` function, for event-based subscriptions
|
||||
* from within reducers.
|
||||
*
|
||||
* @param {Object} reducers
|
||||
* @param {Function} emit
|
||||
* @return {Function}
|
||||
*/
|
||||
function combineEmittingReducers (reducers, emit) {
|
||||
// Wrap each reducer with a wrapper function that calls
|
||||
// the reducer with a third argument, an `emit` function.
|
||||
// Use this rather than a new custom top level reducer that would ultimately
|
||||
// have to replicate redux's `combineReducers` so we only pass in correct state,
|
||||
// the error checking, and other edge cases.
|
||||
function wrapReduce (newReducers, key) {
|
||||
newReducers[key] = (state, action) => reducers[key](state, action, emit);
|
||||
return newReducers;
|
||||
}
|
||||
|
||||
return combineReducers(Object.keys(reducers).reduce(wrapReduce, Object.create(null)));
|
||||
}
|
||||
|
||||
exports.combineEmittingReducers = combineEmittingReducers;
|
21
browser/devtools/shared/vendor/REDUX_LICENSE
vendored
Normal file
21
browser/devtools/shared/vendor/REDUX_LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dan Abramov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -4,12 +4,12 @@
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.fluxify += [
|
||||
'bindActionCreators.js',
|
||||
'dispatcher.js',
|
||||
'logMiddleware.js',
|
||||
'thunkMiddleware.js',
|
||||
'waitUntilService.js'
|
||||
EXTRA_JS_MODULES.devtools.shared.vendor += [
|
||||
'react.js',
|
||||
'redux.js',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
if CONFIG['DEBUG_JS_MODULES']:
|
||||
EXTRA_JS_MODULES.devtools.shared.vendor += [
|
||||
'content/react-dev.js'
|
||||
]
|
610
browser/devtools/shared/vendor/redux.js
vendored
Normal file
610
browser/devtools/shared/vendor/redux.js
vendored
Normal file
@ -0,0 +1,610 @@
|
||||
(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;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _createStore = __webpack_require__(1);
|
||||
|
||||
var _createStore2 = _interopRequireDefault(_createStore);
|
||||
|
||||
var _utilsCombineReducers = __webpack_require__(7);
|
||||
|
||||
var _utilsCombineReducers2 = _interopRequireDefault(_utilsCombineReducers);
|
||||
|
||||
var _utilsBindActionCreators = __webpack_require__(6);
|
||||
|
||||
var _utilsBindActionCreators2 = _interopRequireDefault(_utilsBindActionCreators);
|
||||
|
||||
var _utilsApplyMiddleware = __webpack_require__(5);
|
||||
|
||||
var _utilsApplyMiddleware2 = _interopRequireDefault(_utilsApplyMiddleware);
|
||||
|
||||
var _utilsCompose = __webpack_require__(2);
|
||||
|
||||
var _utilsCompose2 = _interopRequireDefault(_utilsCompose);
|
||||
|
||||
exports.createStore = _createStore2['default'];
|
||||
exports.combineReducers = _utilsCombineReducers2['default'];
|
||||
exports.bindActionCreators = _utilsBindActionCreators2['default'];
|
||||
exports.applyMiddleware = _utilsApplyMiddleware2['default'];
|
||||
exports.compose = _utilsCompose2['default'];
|
||||
|
||||
/***/ },
|
||||
/* 1 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports['default'] = createStore;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _utilsIsPlainObject = __webpack_require__(3);
|
||||
|
||||
var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
INIT: '@@redux/INIT'
|
||||
};
|
||||
|
||||
exports.ActionTypes = ActionTypes;
|
||||
/**
|
||||
* 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} [initialState] 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.
|
||||
*
|
||||
* @returns {Store} A Redux store that lets you read the state, dispatch actions
|
||||
* and subscribe to changes.
|
||||
*/
|
||||
|
||||
function createStore(reducer, initialState) {
|
||||
if (typeof reducer !== 'function') {
|
||||
throw new Error('Expected the reducer to be a function.');
|
||||
}
|
||||
|
||||
var currentReducer = reducer;
|
||||
var currentState = initialState;
|
||||
var listeners = [];
|
||||
var isDispatching = false;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {Function} listener A callback to be invoked on every dispatch.
|
||||
* @returns {Function} A function to remove this change listener.
|
||||
*/
|
||||
function subscribe(listener) {
|
||||
listeners.push(listener);
|
||||
|
||||
return function unsubscribe() {
|
||||
var index = listeners.indexOf(listener);
|
||||
listeners.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`.
|
||||
*
|
||||
* @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 (!_utilsIsPlainObject2['default'](action)) {
|
||||
throw new Error('Actions must be plain objects. Use custom middleware for async actions.');
|
||||
}
|
||||
|
||||
if (isDispatching) {
|
||||
throw new Error('Reducers may not dispatch actions.');
|
||||
}
|
||||
|
||||
try {
|
||||
isDispatching = true;
|
||||
currentState = currentReducer(currentState, action);
|
||||
} finally {
|
||||
isDispatching = false;
|
||||
}
|
||||
|
||||
listeners.slice().forEach(function (listener) {
|
||||
return listener();
|
||||
});
|
||||
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) {
|
||||
currentReducer = nextReducer;
|
||||
dispatch({ type: ActionTypes.INIT });
|
||||
}
|
||||
|
||||
// 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 {
|
||||
dispatch: dispatch,
|
||||
subscribe: subscribe,
|
||||
getState: getState,
|
||||
replaceReducer: replaceReducer
|
||||
};
|
||||
}
|
||||
|
||||
/***/ },
|
||||
/* 2 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/**
|
||||
* Composes single-argument functions from right to left.
|
||||
*
|
||||
* @param {...Function} funcs The functions to compose.
|
||||
* @returns {Function} A function obtained by composing functions from right to
|
||||
* left. For example, compose(f, g, h) is identical to x => h(g(f(x))).
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = compose;
|
||||
|
||||
function compose() {
|
||||
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
funcs[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
return function (arg) {
|
||||
return funcs.reduceRight(function (composed, f) {
|
||||
return f(composed);
|
||||
}, arg);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = exports["default"];
|
||||
|
||||
/***/ },
|
||||
/* 3 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports['default'] = isPlainObject;
|
||||
var fnToString = function fnToString(fn) {
|
||||
return Function.prototype.toString.call(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} obj The object to inspect.
|
||||
* @returns {boolean} True if the argument appears to be a plain object.
|
||||
*/
|
||||
|
||||
function isPlainObject(obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var proto = typeof obj.constructor === 'function' ? Object.getPrototypeOf(obj) : Object.prototype;
|
||||
|
||||
if (proto === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var constructor = proto.constructor;
|
||||
|
||||
return typeof constructor === 'function' && constructor instanceof constructor && fnToString(constructor) === fnToString(Object);
|
||||
}
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
/***/ },
|
||||
/* 4 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/**
|
||||
* Applies a function to every key-value pair inside an object.
|
||||
*
|
||||
* @param {Object} obj The source object.
|
||||
* @param {Function} fn The mapper function that receives the value and the key.
|
||||
* @returns {Object} A new object that contains the mapped values for the keys.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = mapValues;
|
||||
|
||||
function mapValues(obj, fn) {
|
||||
return Object.keys(obj).reduce(function (result, key) {
|
||||
result[key] = fn(obj[key], key);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
module.exports = exports["default"];
|
||||
|
||||
/***/ },
|
||||
/* 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;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _compose = __webpack_require__(2);
|
||||
|
||||
var _compose2 = _interopRequireDefault(_compose);
|
||||
|
||||
/**
|
||||
* 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 (next) {
|
||||
return function (reducer, initialState) {
|
||||
var store = next(reducer, initialState);
|
||||
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
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
/***/ },
|
||||
/* 6 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports['default'] = bindActionCreators;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _utilsMapValues = __webpack_require__(4);
|
||||
|
||||
var _utilsMapValues2 = _interopRequireDefault(_utilsMapValues);
|
||||
|
||||
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) {
|
||||
// eslint-disable-line no-eq-null
|
||||
throw new Error('bindActionCreators expected an object or a function, instead received ' + typeof actionCreators + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
|
||||
}
|
||||
|
||||
return _utilsMapValues2['default'](actionCreators, function (actionCreator) {
|
||||
return bindActionCreator(actionCreator, dispatch);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
/***/ },
|
||||
/* 7 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.__esModule = true;
|
||||
exports['default'] = combineReducers;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
var _createStore = __webpack_require__(1);
|
||||
|
||||
var _utilsIsPlainObject = __webpack_require__(3);
|
||||
|
||||
var _utilsIsPlainObject2 = _interopRequireDefault(_utilsIsPlainObject);
|
||||
|
||||
var _utilsMapValues = __webpack_require__(4);
|
||||
|
||||
var _utilsMapValues2 = _interopRequireDefault(_utilsMapValues);
|
||||
|
||||
var _utilsPick = __webpack_require__(8);
|
||||
|
||||
var _utilsPick2 = _interopRequireDefault(_utilsPick);
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
function getErrorMessage(key, action) {
|
||||
var actionType = action && action.type;
|
||||
var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
|
||||
|
||||
return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.';
|
||||
}
|
||||
|
||||
function verifyStateShape(initialState, currentState) {
|
||||
var reducerKeys = Object.keys(currentState);
|
||||
|
||||
if (reducerKeys.length === 0) {
|
||||
console.error('Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_utilsIsPlainObject2['default'](initialState)) {
|
||||
console.error('initialState has unexpected type of "' + ({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected initialState to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"'));
|
||||
return;
|
||||
}
|
||||
|
||||
var unexpectedKeys = Object.keys(initialState).filter(function (key) {
|
||||
return reducerKeys.indexOf(key) < 0;
|
||||
});
|
||||
|
||||
if (unexpectedKeys.length > 0) {
|
||||
console.error('Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" in initialState will be ignored. ') + ('Expected to find one of the known reducer keys instead: "' + reducerKeys.join('", "') + '"'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 finalReducers = _utilsPick2['default'](reducers, function (val) {
|
||||
return typeof val === 'function';
|
||||
});
|
||||
|
||||
Object.keys(finalReducers).forEach(function (key) {
|
||||
var reducer = finalReducers[key];
|
||||
if (typeof reducer(undefined, { type: _createStore.ActionTypes.INIT }) === '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 = 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.');
|
||||
}
|
||||
});
|
||||
|
||||
var defaultState = _utilsMapValues2['default'](finalReducers, function () {
|
||||
return undefined;
|
||||
});
|
||||
var stateShapeVerified;
|
||||
|
||||
return function combination(state, action) {
|
||||
if (state === undefined) state = defaultState;
|
||||
|
||||
var finalState = _utilsMapValues2['default'](finalReducers, function (reducer, key) {
|
||||
var newState = reducer(state[key], action);
|
||||
if (typeof newState === 'undefined') {
|
||||
throw new Error(getErrorMessage(key, action));
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
|
||||
if (true) {
|
||||
if (!stateShapeVerified) {
|
||||
verifyStateShape(state, finalState);
|
||||
stateShapeVerified = true;
|
||||
}
|
||||
}
|
||||
|
||||
return finalState;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = exports['default'];
|
||||
|
||||
/***/ },
|
||||
/* 8 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/**
|
||||
* Picks key-value pairs from an object where values satisfy a predicate.
|
||||
*
|
||||
* @param {Object} obj The object to pick from.
|
||||
* @param {Function} fn The predicate the values must satisfy to be copied.
|
||||
* @returns {Object} The object with the values that satisfied the predicate.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
exports.__esModule = true;
|
||||
exports["default"] = pick;
|
||||
|
||||
function pick(obj, fn) {
|
||||
return Object.keys(obj).reduce(function (result, key) {
|
||||
if (fn(obj[key])) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
module.exports = exports["default"];
|
||||
|
||||
/***/ }
|
||||
/******/ ])
|
||||
});
|
||||
;
|
@ -18,7 +18,7 @@
|
||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/devtools/d3.js"/>
|
||||
<script type="application/javascript" src="dagre-d3.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/devtools/dagre-d3.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/includes.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/models.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/controller.js"/>
|
||||
|
@ -471,6 +471,11 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
consider translating it as if it said only "Search Settings". -->
|
||||
<!ENTITY changeSearchSettings.button "Change Search Settings">
|
||||
|
||||
<!ENTITY searchInNewTab.label "Search in New Tab">
|
||||
<!ENTITY searchInNewTab.accesskey "T">
|
||||
<!ENTITY searchSetAsDefault.label "Set As Default Search Engine">
|
||||
<!ENTITY searchSetAsDefault.accesskey "D">
|
||||
|
||||
<!ENTITY tabView.commandkey "e">
|
||||
|
||||
<!ENTITY openLinkCmdInTab.label "Open Link in New Tab">
|
||||
|
@ -1,5 +1,6 @@
|
||||
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
|
||||
border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
|
||||
color: -moz-FieldText;
|
||||
background-color: hsla(210, 4%, 10%, 0.07);
|
||||
padding: 6px 0;
|
||||
-moz-padding-start: 44px;
|
||||
|
@ -945,3 +945,15 @@ pref("browser.tabs.showAudioPlayingIcon", true);
|
||||
pref("dom.serviceWorkers.enabled", true);
|
||||
pref("dom.serviceWorkers.interception.enabled", true);
|
||||
#endif
|
||||
|
||||
// The remote content URL where FxAccountsWebChannel messages originate. Must use HTTPS.
|
||||
pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com");
|
||||
|
||||
// The remote URL of the Firefox Account profile server.
|
||||
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
|
||||
|
||||
// The remote URL of the Firefox Account oauth server.
|
||||
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
|
||||
|
||||
// Token server used by Firefox Account-authenticated Sync.
|
||||
pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
|
||||
|
@ -12,6 +12,7 @@ import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
public class AboutPages {
|
||||
// All of our special pages.
|
||||
public static final String ACCOUNTS = "about:accounts";
|
||||
public static final String ADDONS = "about:addons";
|
||||
public static final String CONFIG = "about:config";
|
||||
public static final String DOWNLOADS = "about:downloads";
|
||||
@ -72,6 +73,7 @@ public class AboutPages {
|
||||
}
|
||||
|
||||
private static final String[] DEFAULT_ICON_PAGES = new String[] {
|
||||
ACCOUNTS,
|
||||
ADDONS,
|
||||
CONFIG,
|
||||
DOWNLOADS,
|
||||
|
@ -21,6 +21,7 @@ import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.restrictions.Restriction;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
@ -74,6 +75,16 @@ public class AccountsHelper implements NativeEventListener {
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
|
||||
if (!RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
|
||||
// We register for messages in all contexts; we drop, with a log and an error to JavaScript,
|
||||
// when the profile is restricted. It's better to return errors than silently ignore messages.
|
||||
Log.e(LOGTAG, "Profile is not allowed to modify accounts! Ignoring event: " + event);
|
||||
if (callback != null) {
|
||||
callback.sendError("Profile is not allowed to modify accounts!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
|
||||
AndroidFxAccount fxAccount = null;
|
||||
try {
|
||||
|
@ -10,7 +10,6 @@ import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.PrintHelper;
|
||||
import org.mozilla.gecko.Tabs.TabEvents;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
@ -24,7 +23,6 @@ import org.mozilla.gecko.favicons.LoadFaviconTask;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
|
||||
import org.mozilla.gecko.firstrun.FirstrunPane;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
|
||||
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
@ -97,7 +95,6 @@ import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
@ -2581,7 +2578,7 @@ public class BrowserApp extends GeckoApp
|
||||
if (mFirstrunPane == null) {
|
||||
final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
|
||||
mFirstrunPane = (FirstrunPane) firstrunPagerStub.inflate();
|
||||
mFirstrunPane.load(getSupportFragmentManager());
|
||||
mFirstrunPane.load(getApplicationContext(), getSupportFragmentManager());
|
||||
mFirstrunPane.registerOnFinishListener(new FirstrunPane.OnFinishListener() {
|
||||
@Override
|
||||
public void onFinish() {
|
||||
|
@ -164,6 +164,9 @@ public interface TelemetryContract {
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
PAGEACTION("pageaction"),
|
||||
|
||||
// Action triggered from one of a series of views, such as ViewPager.
|
||||
PANEL("panel"),
|
||||
|
||||
// Action triggered from a settings screen.
|
||||
SETTINGS("settings"),
|
||||
|
||||
@ -207,6 +210,9 @@ public interface TelemetryContract {
|
||||
// Awesomescreen (including frecency search) is active.
|
||||
AWESOMESCREEN("awesomescreen.1"),
|
||||
|
||||
// Used to tag experiments being run.
|
||||
EXPERIMENT("experiment.1"),
|
||||
|
||||
// Started the very first time we believe the application has been launched.
|
||||
FIRSTRUN("firstrun.1"),
|
||||
|
||||
|
@ -11,19 +11,23 @@ import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import com.nineoldandroids.animation.Animator;
|
||||
import com.nineoldandroids.animation.AnimatorSet;
|
||||
import com.nineoldandroids.animation.ObjectAnimator;
|
||||
import com.nineoldandroids.view.ViewHelper;
|
||||
|
||||
import org.mozilla.gecko.RestrictedProfiles;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FirstrunPager extends ViewPager {
|
||||
|
||||
private Context context;
|
||||
protected FirstrunPane.OnFinishListener listener;
|
||||
protected FirstrunPanel.PagerNavigation pagerNavigation;
|
||||
|
||||
public FirstrunPager(Context context) {
|
||||
this(context, null);
|
||||
@ -34,17 +38,44 @@ public class FirstrunPager extends ViewPager {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void load(FragmentManager fm, FirstrunPane.OnFinishListener listener) {
|
||||
final List<FirstrunPagerConfig.FirstrunPanel> panels;
|
||||
public void load(Context appContext, FragmentManager fm, final FirstrunPane.OnFinishListener onFinishListener) {
|
||||
final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
|
||||
|
||||
if (RestrictedProfiles.isUserRestricted(context)) {
|
||||
panels = FirstrunPagerConfig.getRestricted();
|
||||
} else {
|
||||
panels = FirstrunPagerConfig.getDefault();
|
||||
panels = FirstrunPagerConfig.getDefault(appContext);
|
||||
}
|
||||
|
||||
setAdapter(new ViewPagerAdapter(fm, panels));
|
||||
this.listener = listener;
|
||||
this.pagerNavigation = new FirstrunPanel.PagerNavigation() {
|
||||
@Override
|
||||
public void next() {
|
||||
final int currentPage = FirstrunPager.this.getCurrentItem();
|
||||
if (currentPage < FirstrunPager.this.getChildCount() - 1) {
|
||||
FirstrunPager.this.setCurrentItem(currentPage + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (onFinishListener != null) {
|
||||
onFinishListener.onFinish();
|
||||
}
|
||||
}
|
||||
};
|
||||
addOnPageChangeListener(new OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int i, float v, int i1) {}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int i) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.PANEL, "onboarding." + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int i) {}
|
||||
});
|
||||
|
||||
animateLoad();
|
||||
}
|
||||
@ -73,17 +104,23 @@ public class FirstrunPager extends ViewPager {
|
||||
}
|
||||
|
||||
private class ViewPagerAdapter extends FragmentPagerAdapter {
|
||||
private List<FirstrunPagerConfig.FirstrunPanel> panels;
|
||||
private final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
|
||||
private final Fragment[] fragments;
|
||||
|
||||
public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanel> panels) {
|
||||
public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanelConfig> panels) {
|
||||
super(fm);
|
||||
this.panels = panels;
|
||||
this.fragments = new Fragment[panels.size()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int i) {
|
||||
final Fragment fragment = Fragment.instantiate(context, panels.get(i).getClassname());
|
||||
((FirstrunPanel) fragment).setOnFinishListener(listener);
|
||||
Fragment fragment = this.fragments[i];
|
||||
if (fragment == null) {
|
||||
fragment = Fragment.instantiate(context, panels.get(i).getClassname());
|
||||
((FirstrunPanel) fragment).setPagerNavigation(pagerNavigation);
|
||||
fragments[i] = fragment;
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
@ -5,27 +5,65 @@
|
||||
|
||||
package org.mozilla.gecko.firstrun;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class FirstrunPagerConfig {
|
||||
public static List<FirstrunPanel> getDefault() {
|
||||
final List<FirstrunPanel> panels = new LinkedList<>();
|
||||
panels.add(new FirstrunPanel(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
public static final String LOGTAG = "FirstrunPagerConfig";
|
||||
public static final String ONBOARDING_A = "onboarding-a";
|
||||
public static final String ONBOARDING_B = "onboarding-b";
|
||||
|
||||
public static List<FirstrunPanelConfig> getDefault(Context context) {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
if (isInExperimentLocal(context, ONBOARDING_A)) {
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, ONBOARDING_A);
|
||||
} else if (isInExperimentLocal(context, ONBOARDING_B)) {
|
||||
// Strings used for first run, pulled from existing strings.
|
||||
panels.add(new FirstrunPanelConfig(ImportPanel.class.getName(), ImportPanel.TITLE_RES));
|
||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, ONBOARDING_B);
|
||||
} else {
|
||||
Log.d(LOGTAG, "Not in an experiment!");
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
}
|
||||
|
||||
return panels;
|
||||
}
|
||||
|
||||
public static List<FirstrunPanel> getRestricted() {
|
||||
final List<FirstrunPanel> panels = new LinkedList<>();
|
||||
panels.add(new FirstrunPanel(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
|
||||
/*
|
||||
* Wrapper method for using local bucketing rather than server-side.
|
||||
* This needs to match the server-side bucketing used on mozilla-switchboard.herokuapp.com.
|
||||
*/
|
||||
private static boolean isInExperimentLocal(Context context, String name) {
|
||||
if (AppConstants.MOZ_SWITCHBOARD) {
|
||||
if (SwitchBoard.isInBucket(context, 0, 50)) {
|
||||
return ONBOARDING_A.equals(name);
|
||||
} else if (SwitchBoard.isInBucket(context, 50, 100)) {
|
||||
return ONBOARDING_B.equals(name);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<FirstrunPanelConfig> getRestricted() {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
|
||||
return panels;
|
||||
}
|
||||
|
||||
public static class FirstrunPanel {
|
||||
public static class FirstrunPanelConfig {
|
||||
private String classname;
|
||||
private int titleRes;
|
||||
|
||||
public FirstrunPanel(String resource, int titleRes) {
|
||||
public FirstrunPanelConfig(String resource, int titleRes) {
|
||||
this.classname= resource;
|
||||
this.titleRes = titleRes;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import com.nineoldandroids.animation.Animator;
|
||||
import com.nineoldandroids.animation.AnimatorListenerAdapter;
|
||||
import com.nineoldandroids.animation.ObjectAnimator;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
|
||||
public class FirstrunPane extends LinearLayout {
|
||||
@ -35,10 +37,10 @@ public class FirstrunPane extends LinearLayout {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void load(FragmentManager fm) {
|
||||
public void load(Context appContext, FragmentManager fm) {
|
||||
visible = true;
|
||||
pager = (FirstrunPager) findViewById(R.id.firstrun_pager);
|
||||
pager.load(fm, new OnFinishListener() {
|
||||
pager.load(appContext, fm, new OnFinishListener() {
|
||||
@Override
|
||||
public void onFinish() {
|
||||
hide();
|
||||
@ -57,6 +59,10 @@ public class FirstrunPane extends LinearLayout {
|
||||
onFinishListener.onFinish();
|
||||
}
|
||||
animateHide();
|
||||
|
||||
// Stop all versions of firstrun A/B sessions.
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FirstrunPagerConfig.ONBOARDING_A);
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FirstrunPagerConfig.ONBOARDING_B);
|
||||
}
|
||||
|
||||
private void animateHide() {
|
||||
|
@ -10,15 +10,25 @@ import android.support.v4.app.Fragment;
|
||||
public class FirstrunPanel extends Fragment {
|
||||
|
||||
public static final int TITLE_RES = -1;
|
||||
protected FirstrunPane.OnFinishListener onFinishListener;
|
||||
public interface PagerNavigation {
|
||||
void next();
|
||||
void finish();
|
||||
}
|
||||
protected PagerNavigation pagerNavigation;
|
||||
|
||||
public void setOnFinishListener(FirstrunPane.OnFinishListener listener) {
|
||||
this.onFinishListener = listener;
|
||||
public void setPagerNavigation(PagerNavigation listener) {
|
||||
this.pagerNavigation = listener;
|
||||
}
|
||||
|
||||
protected void next() {
|
||||
if (pagerNavigation != null) {
|
||||
pagerNavigation.next();
|
||||
}
|
||||
}
|
||||
|
||||
protected void close() {
|
||||
if (onFinishListener != null) {
|
||||
onFinishListener.onFinish();
|
||||
if (pagerNavigation != null) {
|
||||
pagerNavigation.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
159
mobile/android/base/firstrun/ImportPanel.java
Normal file
159
mobile/android/base/firstrun/ImportPanel.java
Normal file
@ -0,0 +1,159 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.firstrun;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.preferences.AndroidImport;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportPanel extends FirstrunPanel {
|
||||
public static final String LOGTAG = "GeckoImportPanel";
|
||||
public static final int TITLE_RES = R.string.firstrun_import_title;
|
||||
private static final int AUTOADVANCE_DELAY_MS = 1500;
|
||||
|
||||
// These match the item positions in R.array.pref_import_android_entries.
|
||||
private static int BOOKMARKS_INDEX = 0;
|
||||
private static int HISTORY_INDEX = 1;
|
||||
|
||||
private ImageView confirmImage;
|
||||
private Button choiceButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_import_fragment, container, false);
|
||||
choiceButton = (Button) root.findViewById(R.id.import_action_button);
|
||||
confirmImage = (ImageView) root.findViewById(R.id.confirm_check);
|
||||
choiceButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
final List<Integer> checked = new ArrayList<>(Arrays.asList(0, 1));
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-import-action");
|
||||
builder.setTitle(R.string.firstrun_import_action)
|
||||
.setMultiChoiceItems(R.array.pref_import_android_entries, makeBooleanArray(R.array.pref_import_android_defaults), new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int index, boolean isChecked) {
|
||||
// Add telemetry for toggling checkboxes.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, TelemetryContract.Method.DIALOG, "firstrun-import-dialog-checkbox");
|
||||
if (isChecked && !checked.contains(index)) {
|
||||
checked.add(index);
|
||||
} else if (!isChecked && checked.contains(index)) {
|
||||
checked.remove(checked.indexOf(index));
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BUTTON, "firstrun-import-dialog");
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.firstrun_import_dialog_button, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
final boolean importBookmarks = checked.contains(BOOKMARKS_INDEX);
|
||||
final boolean importHistory = checked.contains(HISTORY_INDEX);
|
||||
|
||||
runImport(importBookmarks, importHistory);
|
||||
|
||||
// Telemetry for what options are confirmed.
|
||||
final int importState = (importBookmarks ? 1 : 0) + (importHistory ? 2 : 0);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.BUTTON, "firstrun-import-dialog-" + importState);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
});
|
||||
|
||||
root.findViewById(R.id.import_link).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-import-next");
|
||||
pagerNavigation.next();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private boolean[] makeBooleanArray(int resId) {
|
||||
final String[] defaults = getResources().getStringArray(resId);
|
||||
final boolean[] booleanArray = new boolean[defaults.length];
|
||||
for (int i = 0; i < defaults.length; i++) {
|
||||
booleanArray[i] = defaults[i].equals("true");
|
||||
}
|
||||
return booleanArray;
|
||||
}
|
||||
|
||||
protected void runImport(final boolean doBookmarks, final boolean doHistory) {
|
||||
Log.d(LOGTAG, "Importing Android history/bookmarks");
|
||||
if (!doBookmarks && !doHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Context context = getActivity();
|
||||
final String dialogTitle = context.getString(R.string.firstrun_import_progress_title);
|
||||
|
||||
final ProgressDialog dialog =
|
||||
ProgressDialog.show(context,
|
||||
dialogTitle,
|
||||
context.getString(R.string.bookmarkhistory_import_wait),
|
||||
true);
|
||||
|
||||
final Runnable stopCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
confirmImage.setVisibility(View.VISIBLE);
|
||||
choiceButton.setVisibility(View.GONE);
|
||||
|
||||
dialog.dismiss();
|
||||
|
||||
ThreadUtils.postDelayedToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
next();
|
||||
}
|
||||
}, AUTOADVANCE_DELAY_MS);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ThreadUtils.postToBackgroundThread(
|
||||
// Constructing AndroidImport may need finding the profile,
|
||||
// which hits disk, so it needs to go into a Runnable too.
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new AndroidImport(context, stopCallback, doBookmarks, doHistory).run();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
49
mobile/android/base/firstrun/SyncPanel.java
Normal file
49
mobile/android/base/firstrun/SyncPanel.java
Normal file
@ -0,0 +1,49 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.firstrun;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
||||
|
||||
public class SyncPanel extends FirstrunPanel {
|
||||
// XXX: To simplify localization, this uses the pref_sync string. If this is used in the final product, add a new string to Nightly.
|
||||
public static final int TITLE_RES = R.string.pref_sync;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_sync_fragment, container, false);
|
||||
// TODO: Update id names.
|
||||
root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
|
||||
|
||||
final Intent accountIntent = new Intent(getActivity(), FxAccountGetStartedActivity.class);
|
||||
startActivity(accountIntent);
|
||||
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-browser");
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -10,10 +10,12 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
|
||||
|
||||
public class WelcomePanel extends FirstrunPanel {
|
||||
public static final int TITLE_RES = R.string.firstrun_panel_title_welcome;
|
||||
@ -27,6 +29,7 @@ public class WelcomePanel extends FirstrunPanel {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
|
||||
|
||||
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_FIRSTRUN);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
|
@ -100,4 +100,8 @@ public class FxAccountConstants {
|
||||
public static final String ACTION_FXA_GET_STARTED = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_GET_STARTED";
|
||||
public static final String ACTION_FXA_STATUS = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_STATUS";
|
||||
public static final String ACTION_FXA_UPDATE_CREDENTIALS = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_UPDATE_CREDENTIALS";
|
||||
|
||||
public static final String ENDPOINT_PREFERENCES = "preferences";
|
||||
public static final String ENDPOINT_NOTIFICATION = "notification";
|
||||
public static final String ENDPOINT_FIRSTRUN = "firstrun";
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
public class FxAccountConfirmAccountActivityWeb extends FxAccountWebFlowActivity {
|
||||
public FxAccountConfirmAccountActivityWeb() {
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "settings");
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "manage");
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
public class FxAccountGetStartedActivityWeb extends FxAccountWebFlowActivity {
|
||||
public FxAccountGetStartedActivityWeb() {
|
||||
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signin");
|
||||
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signup");
|
||||
}
|
||||
}
|
||||
|
@ -249,6 +249,7 @@ public class FxAccountStatusFragment
|
||||
|
||||
if (preference == needsPasswordPreference) {
|
||||
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
|
||||
final Bundle extras = getExtrasForAccount();
|
||||
if (extras != null) {
|
||||
intent.putExtras(extras);
|
||||
@ -263,6 +264,7 @@ public class FxAccountStatusFragment
|
||||
|
||||
if (preference == needsFinishMigratingPreference) {
|
||||
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
|
||||
final Bundle extras = getExtrasForAccount();
|
||||
if (extras != null) {
|
||||
intent.putExtras(extras);
|
||||
@ -284,6 +286,7 @@ public class FxAccountStatusFragment
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
/**
|
||||
@ -18,6 +18,13 @@ public class FxAccountWebFlowActivity extends FxAccountAbstractActivity {
|
||||
protected static final String LOG_TAG = FxAccountWebFlowActivity.class.getSimpleName();
|
||||
|
||||
protected static final String ABOUT_ACCOUNTS = "about:accounts";
|
||||
|
||||
public static final String EXTRA_ENDPOINT = "entrypoint";
|
||||
|
||||
protected static final String[] EXTRAS_TO_PASSTHROUGH = new String[] {
|
||||
EXTRA_ENDPOINT,
|
||||
};
|
||||
|
||||
private final String action;
|
||||
private final String extras;
|
||||
|
||||
@ -49,8 +56,28 @@ public class FxAccountWebFlowActivity extends FxAccountAbstractActivity {
|
||||
if (redirected) {
|
||||
return true;
|
||||
}
|
||||
ActivityUtils.openURLInFennec(getApplicationContext(),
|
||||
ABOUT_ACCOUNTS + "?action=" + action + extras);
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(ABOUT_ACCOUNTS);
|
||||
sb.append("?action=");
|
||||
sb.append(action);
|
||||
sb.append(extras);
|
||||
|
||||
// Pass through a set of known string values from intent extras to about:accounts.
|
||||
final Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
for (String key : EXTRAS_TO_PASSTHROUGH) {
|
||||
final String value = intent.getStringExtra(key);
|
||||
if (value != null) {
|
||||
sb.append("&");
|
||||
sb.append(key);
|
||||
sb.append("=");
|
||||
sb.append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActivityUtils.openURLInFennec(getApplicationContext(), sb.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.Action;
|
||||
@ -93,6 +94,9 @@ public class FxAccountNotificationManager {
|
||||
text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
notificationIntent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
|
||||
}
|
||||
|
||||
notificationIntent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_NOTIFICATION);
|
||||
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||
FxAccountUtils.pii(LOG_TAG, "And text: " + text);
|
||||
|
||||
|
@ -6,12 +6,22 @@
|
||||
<!ENTITY no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
|
||||
<!ENTITY error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
|
||||
|
||||
<!ENTITY firstrun_panel_title_welcome "Welcome">
|
||||
<!ENTITY onboard_start_message3 "Browse with &brandShortName;">
|
||||
<!ENTITY onboard_start_subtext3 "Make your mobile Web browsing experience truly your own.">
|
||||
<!ENTITY onboard_start_button_account "Sign in to &brandShortName;">
|
||||
<!ENTITY onboard_start_button_browser "Start Browsing">
|
||||
<!ENTITY firstrun_button_next "Next">
|
||||
|
||||
<!ENTITY onboard_start_restricted1 "Stay safe and in control with this simplified version of &brandShortName;.">
|
||||
|
||||
<!ENTITY firstrun_import_title "Import">
|
||||
<!ENTITY firstrun_import_message "Welcome to &brandShortName;">
|
||||
<!ENTITY firstrun_import_subtext "Import your bookmarks and history from another browser.">
|
||||
<!ENTITY firstrun_import_action "Transfer to &brandShortName;">
|
||||
<!ENTITY firstrun_import_dialog_button "Transfer">
|
||||
<!ENTITY firstrun_import_progress_title "Transferring">
|
||||
|
||||
<!-- Localization note: These are used as the titles of different pages on the home screen.
|
||||
They are automatically converted to all caps by the Android platform. -->
|
||||
<!ENTITY bookmarks_title "Bookmarks">
|
||||
@ -486,8 +496,6 @@ size. -->
|
||||
<!ENTITY button_clear "Clear">
|
||||
<!ENTITY button_copy "Copy">
|
||||
|
||||
<!ENTITY firstrun_panel_title_welcome "Welcome">
|
||||
|
||||
<!ENTITY home_top_sites_title "Top Sites">
|
||||
<!-- Localization note (home_top_sites_add): This string is used as placeholder
|
||||
text underneath empty thumbnails in the Top Sites page on about:home. -->
|
||||
|
@ -231,7 +231,9 @@ gbjar.sources += [
|
||||
'firstrun/FirstrunPagerConfig.java',
|
||||
'firstrun/FirstrunPane.java',
|
||||
'firstrun/FirstrunPanel.java',
|
||||
'firstrun/ImportPanel.java',
|
||||
'firstrun/RestrictedWelcomePanel.java',
|
||||
'firstrun/SyncPanel.java',
|
||||
'firstrun/WelcomePanel.java',
|
||||
'FormAssistPopup.java',
|
||||
'GeckoAccessibility.java',
|
||||
@ -498,6 +500,7 @@ gbjar.sources += [
|
||||
'toolbar/PageActionLayout.java',
|
||||
'toolbar/PhoneTabsButton.java',
|
||||
'toolbar/ShapedButton.java',
|
||||
'toolbar/ShapedButtonFrameLayout.java',
|
||||
'toolbar/SiteIdentityPopup.java',
|
||||
'toolbar/TabCounter.java',
|
||||
'toolbar/ToolbarDisplayLayout.java',
|
||||
|
@ -23,7 +23,7 @@ import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class AndroidImport implements Runnable {
|
||||
public class AndroidImport implements Runnable {
|
||||
/**
|
||||
* The Android M SDK removed several fields and methods from android.provider.Browser. This class is used as a
|
||||
* replacement to support building with the new SDK but at the same time still use these fields on lower Android
|
||||
|
@ -13,6 +13,7 @@ import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.TelemetryContract.Method;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
|
||||
|
||||
@ -40,6 +41,7 @@ class SyncPreference extends Preference {
|
||||
|
||||
private void launchFxASetup() {
|
||||
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
mContext.startActivity(intent);
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
@ -3,6 +3,8 @@
|
||||
- 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/. -->
|
||||
|
||||
<!-- If you change this view, update ShapedButton*,
|
||||
which dynamically resets to this view. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true"
|
||||
@ -11,7 +13,7 @@
|
||||
<item android:state_focused="true"
|
||||
android:state_pressed="false"
|
||||
android:drawable="@color/highlight_shaped_focused"/>
|
||||
|
||||
|
||||
<item android:drawable="@color/text_and_tabs_tray_grey"/>
|
||||
|
||||
</selector>
|
||||
|
@ -36,7 +36,7 @@
|
||||
android:src="@drawable/url_bar_translating_edge"
|
||||
android:scaleType="fitXY"/>
|
||||
|
||||
<org.mozilla.gecko.widget.themed.ThemedFrameLayout
|
||||
<org.mozilla.gecko.toolbar.ShapedButtonFrameLayout
|
||||
android:id="@+id/menu"
|
||||
style="@style/UrlBar.ImageButton"
|
||||
android:layout_alignParentRight="true"
|
||||
@ -54,7 +54,7 @@
|
||||
android:src="@drawable/menu"
|
||||
android:tint="@color/tabs_tray_icon_grey"/>
|
||||
|
||||
</org.mozilla.gecko.widget.themed.ThemedFrameLayout>
|
||||
</org.mozilla.gecko.toolbar.ShapedButtonFrameLayout>
|
||||
|
||||
<org.mozilla.gecko.toolbar.PhoneTabsButton android:id="@+id/tabs"
|
||||
style="@style/UrlBar.ImageButton"
|
||||
|
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-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/. -->
|
||||
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/firstrun_min_height"
|
||||
android:background="@color/android:white"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/link_blue">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/firstrun_background_coffee"/>
|
||||
|
||||
<ImageView android:layout_width="@dimen/firstrun_brand_size"
|
||||
android:layout_height="@dimen/firstrun_brand_size"
|
||||
android:src="@drawable/large_icon"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="30dp"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
|
||||
android:text="@string/firstrun_import_message"/>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="30dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
|
||||
android:text="@string/firstrun_import_subtext"/>
|
||||
|
||||
<Button android:id="@+id/import_action_button"
|
||||
style="@style/Widget.Firstrun.Button"
|
||||
android:background="@drawable/button_background_action_orange_round"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/firstrun_import_action"/>
|
||||
|
||||
<ImageView android:id="@+id/confirm_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="60dp"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/overlay_check"/>
|
||||
|
||||
<TextView android:id="@+id/import_link"
|
||||
android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
|
||||
android:text="@string/firstrun_button_next"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-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/. -->
|
||||
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/firstrun_min_height"
|
||||
android:background="@color/android:white"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/link_blue">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/firstrun_background_devices"/>
|
||||
|
||||
<ImageView android:layout_width="@dimen/firstrun_brand_size"
|
||||
android:layout_height="@dimen/firstrun_brand_size"
|
||||
android:src="@drawable/large_icon"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="30dp"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
|
||||
android:text="@string/firstrun_welcome_message"/>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="30dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
|
||||
android:text="@string/firstrun_welcome_subtext"/>
|
||||
|
||||
<Button android:id="@+id/welcome_account"
|
||||
style="@style/Widget.Firstrun.Button"
|
||||
android:background="@drawable/button_background_action_orange_round"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/firstrun_welcome_button_account"/>
|
||||
|
||||
<TextView android:id="@+id/welcome_browse"
|
||||
android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
|
||||
android:text="@string/firstrun_welcome_button_browser"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -38,7 +38,8 @@
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_logins">
|
||||
|
||||
<org.mozilla.gecko.preferences.LinkPreference android:title="@string/pref_manage_logins"
|
||||
<org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.signon.manage"
|
||||
android:title="@string/pref_manage_logins"
|
||||
url="about:logins"/>
|
||||
|
||||
<CheckBoxPreference android:key="signon.rememberSignons"
|
||||
|
@ -37,13 +37,24 @@
|
||||
<string name="no_space_to_start_error">&no_space_to_start_error;</string>
|
||||
<string name="error_loading_file">&error_loading_file;</string>
|
||||
|
||||
|
||||
<string name="firstrun_panel_title_welcome">&firstrun_panel_title_welcome;</string>
|
||||
<string name="firstrun_welcome_message">&onboard_start_message3;</string>
|
||||
<string name="firstrun_welcome_subtext">&onboard_start_subtext3;</string>
|
||||
<string name="firstrun_welcome_button_account">&onboard_start_button_account;</string>
|
||||
<string name="firstrun_welcome_button_browser">&onboard_start_button_browser;</string>
|
||||
<string name="firstrun_button_next">&firstrun_button_next;</string>
|
||||
<string name="firstrun_empty_contentDescription"></string>
|
||||
|
||||
<string name="firstrun_welcome_restricted">&onboard_start_restricted1;</string>
|
||||
|
||||
<string name="firstrun_import_title">&firstrun_import_title;</string>
|
||||
<string name="firstrun_import_message">&firstrun_import_message;</string>
|
||||
<string name="firstrun_import_subtext">&firstrun_import_subtext;</string>
|
||||
<string name="firstrun_import_action">&firstrun_import_action;</string>
|
||||
<string name="firstrun_import_dialog_button">&firstrun_import_dialog_button;</string>
|
||||
<string name="firstrun_import_progress_title">&firstrun_import_progress_title;</string>
|
||||
|
||||
<string name="bookmarks_title">&bookmarks_title;</string>
|
||||
<string name="history_title">&history_title;</string>
|
||||
<string name="reading_list_title">&reading_list_title;</string>
|
||||
@ -409,8 +420,6 @@
|
||||
<string name="button_no">&button_no;</string>
|
||||
<string name="button_copy">&button_copy;</string>
|
||||
|
||||
<string name="firstrun_panel_title_welcome">&firstrun_panel_title_welcome;</string>
|
||||
|
||||
<string name="home_title">&home_title;</string>
|
||||
<string name="home_top_sites_title">&home_top_sites_title;</string>
|
||||
<string name="home_top_sites_add">&home_top_sites_add;</string>
|
||||
|
@ -23,6 +23,7 @@ import android.text.style.ClickableSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.tabqueue.TabQueueDispatcher;
|
||||
|
||||
public class ActivityUtils {
|
||||
private static final String LOG_TAG = "ActivityUtils";
|
||||
@ -64,6 +65,7 @@ public class ActivityUtils {
|
||||
}
|
||||
intent.setClassName(GlobalConstants.BROWSER_INTENT_PACKAGE, GlobalConstants.BROWSER_INTENT_CLASS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(TabQueueDispatcher.SKIP_TAB_QUEUE_FLAG, true);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,10 @@ import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A ImageButton with a custom drawn path and lightweight theme support. Note that {@link ShapedButtonFrameLayout}
|
||||
* copies the lwt support so if you change it here, you should probably change it there.
|
||||
*/
|
||||
public class ShapedButton extends ThemedImageButton
|
||||
implements CanvasDelegate.DrawManager {
|
||||
|
||||
|
74
mobile/android/base/toolbar/ShapedButtonFrameLayout.java
Normal file
74
mobile/android/base/toolbar/ShapedButtonFrameLayout.java
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.toolbar;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
|
||||
import org.mozilla.gecko.util.ColorUtils;
|
||||
import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/** A FrameLayout with lightweight theme support. Note that {@link ShapedButton}'s lwt support is basically the same so
|
||||
* if you change it here, you should probably change it there. Note also that this doesn't have ShapedButton's path code
|
||||
* so shouldn't have "ShapedButton" in the name, but I wanted to make the connection apparent so I left it.
|
||||
*/
|
||||
public class ShapedButtonFrameLayout extends ThemedFrameLayout {
|
||||
|
||||
public ShapedButtonFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// The drawable is constructed as per @drawable/shaped_button.
|
||||
@Override
|
||||
public void onLightweightThemeChanged() {
|
||||
final int background = ColorUtils.getColor(getContext(), R.color.text_and_tabs_tray_grey);
|
||||
final LightweightThemeDrawable lightWeight = getTheme().getColorDrawable(this, background);
|
||||
|
||||
if (lightWeight == null)
|
||||
return;
|
||||
|
||||
lightWeight.setAlpha(34, 34);
|
||||
|
||||
final StateListDrawable stateList = new StateListDrawable();
|
||||
stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.highlight_shaped));
|
||||
stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.highlight_shaped_focused));
|
||||
stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
|
||||
stateList.addState(EMPTY_STATE_SET, lightWeight);
|
||||
|
||||
setBackgroundDrawable(stateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.shaped_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundDrawable(Drawable drawable) {
|
||||
if (getBackground() == null || drawable == null) {
|
||||
super.setBackgroundDrawable(drawable);
|
||||
return;
|
||||
}
|
||||
|
||||
int[] padding = new int[] { getPaddingLeft(),
|
||||
getPaddingTop(),
|
||||
getPaddingRight(),
|
||||
getPaddingBottom()
|
||||
};
|
||||
drawable.setLevel(getBackground().getLevel());
|
||||
super.setBackgroundDrawable(drawable);
|
||||
|
||||
setPadding(padding[0], padding[1], padding[2], padding[3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundResource(int resId) {
|
||||
setBackgroundDrawable(getResources().getDrawable(resId));
|
||||
}
|
||||
}
|
327
mobile/android/chrome/content/aboutAccounts.js
Normal file
327
mobile/android/chrome/content/aboutAccounts.js
Normal file
@ -0,0 +1,327 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Wrap a remote fxa-content-server.
|
||||
*
|
||||
* An about:accounts tab loads and displays an fxa-content-server page,
|
||||
* depending on the current Android Account status and an optional 'action'
|
||||
* parameter.
|
||||
*
|
||||
* We show a spinner while the remote iframe is loading. We expect the
|
||||
* WebChannel message listening to the fxa-content-server to send this tab's
|
||||
* <browser>'s messageManager a LOADED message when the remote iframe provides
|
||||
* the WebChannel LOADED message. See the messageManager registration and the
|
||||
* |loadedDeferred| promise. This loosely couples the WebChannel implementation
|
||||
* and about:accounts! (We need this coupling in order to distinguish
|
||||
* WebChannel LOADED messages produced by multiple about:accounts tabs.)
|
||||
*
|
||||
* We capture error conditions by accessing the inner nsIWebNavigation of the
|
||||
* iframe directly.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components; /*global Components */
|
||||
|
||||
Cu.import("resource://gre/modules/Accounts.jsm"); /*global Accounts */
|
||||
Cu.import("resource://gre/modules/PromiseUtils.jsm"); /*global PromiseUtils */
|
||||
Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
|
||||
|
||||
const ACTION_URL_PARAM = "action";
|
||||
|
||||
const COMMAND_LOADED = "fxaccounts:loaded";
|
||||
|
||||
const log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccounts");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls",
|
||||
"@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
|
||||
|
||||
// Shows the toplevel element with |id| to be shown - all other top-level
|
||||
// elements are hidden.
|
||||
// If |id| is 'spinner', then 'remote' is also shown, with opacity 0.
|
||||
function show(id) {
|
||||
let allTop = document.querySelectorAll(".toplevel");
|
||||
for (let elt of allTop) {
|
||||
if (elt.getAttribute("id") == id) {
|
||||
elt.style.display = 'block';
|
||||
} else {
|
||||
elt.style.display = 'none';
|
||||
}
|
||||
}
|
||||
if (id == 'spinner') {
|
||||
document.getElementById('remote').style.display = 'block';
|
||||
document.getElementById('remote').style.opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var loadedDeferred = null;
|
||||
|
||||
// We have a new load starting. Replace the existing promise with a new one,
|
||||
// and queue up the transition to remote content.
|
||||
function deferTransitionToRemoteAfterLoaded() {
|
||||
log.d('Waiting for LOADED message.');
|
||||
loadedDeferred = PromiseUtils.defer();
|
||||
loadedDeferred.promise.then(() => {
|
||||
document.getElementById("remote").style.opacity = 0;
|
||||
show("remote");
|
||||
document.getElementById("remote").style.opacity = 1;
|
||||
});
|
||||
}
|
||||
|
||||
function handleLoadedMessage(message) {
|
||||
log.d('Got LOADED message!');
|
||||
loadedDeferred.resolve();
|
||||
};
|
||||
|
||||
let wrapper = {
|
||||
iframe: null,
|
||||
|
||||
url: null,
|
||||
|
||||
init: function (url) {
|
||||
deferTransitionToRemoteAfterLoaded();
|
||||
|
||||
let iframe = document.getElementById("remote");
|
||||
this.iframe = iframe;
|
||||
this.iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
|
||||
let docShell = this.iframe.frameLoader.docShell;
|
||||
docShell.QueryInterface(Ci.nsIWebProgress);
|
||||
docShell.addProgressListener(this.iframeListener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
|
||||
this.url = url;
|
||||
// Set the iframe's location with loadURI/LOAD_FLAGS_BYPASS_HISTORY to
|
||||
// avoid having a new history entry being added.
|
||||
let webNav = iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
|
||||
},
|
||||
|
||||
retry: function () {
|
||||
deferTransitionToRemoteAfterLoaded();
|
||||
|
||||
let webNav = this.iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(this.url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
|
||||
},
|
||||
|
||||
iframeListener: {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISupports]),
|
||||
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
|
||||
let failure = false;
|
||||
|
||||
// Captive portals sometimes redirect users
|
||||
if ((aState & Ci.nsIWebProgressListener.STATE_REDIRECTING)) {
|
||||
failure = true;
|
||||
} else if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
|
||||
if (aRequest instanceof Ci.nsIHttpChannel) {
|
||||
try {
|
||||
failure = aRequest.responseStatus != 200;
|
||||
} catch (e) {
|
||||
failure = aStatus != Components.results.NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calling cancel() will raise some OnStateChange notifications by itself,
|
||||
// so avoid doing that more than once
|
||||
if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
|
||||
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
show("networkError");
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
||||
if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
|
||||
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
show("networkError");
|
||||
}
|
||||
},
|
||||
|
||||
onProgressChange: function() {},
|
||||
onStatusChange: function() {},
|
||||
onSecurityChange: function() {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
function retry() {
|
||||
log.i("Retrying.");
|
||||
show("spinner");
|
||||
wrapper.retry();
|
||||
}
|
||||
|
||||
function openPrefs() {
|
||||
log.i("Opening Sync preferences.");
|
||||
// If an Android Account exists, this will open the Status Activity.
|
||||
// Otherwise, it will begin the Get Started flow. This should only be shown
|
||||
// when an Account actually exists.
|
||||
Accounts.launchSetup();
|
||||
}
|
||||
|
||||
function getURLForAction(action, urlParams) {
|
||||
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
|
||||
url = url + (url.endsWith("/") ? "" : "/") + action;
|
||||
const CONTEXT = "fx_fennec_v1";
|
||||
// The only service managed by Fennec, to date, is Firefox Sync.
|
||||
const SERVICE = "sync";
|
||||
urlParams = urlParams || new URLSearchParams("");
|
||||
urlParams.set('service', SERVICE);
|
||||
urlParams.set('context', CONTEXT);
|
||||
// Ideally we'd just merge urlParams with new URL(url).searchParams, but our
|
||||
// URLSearchParams implementation doesn't support iteration (bug 1085284).
|
||||
let urlParamStr = urlParams.toString();
|
||||
if (urlParamStr) {
|
||||
url += (url.includes("?") ? "&" : "?") + urlParamStr;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function updateDisplayedEmail(user) {
|
||||
let emailDiv = document.getElementById("email");
|
||||
if (emailDiv && user) {
|
||||
emailDiv.textContent = user.email;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Test for restrictions before getFirefoxAccount(), since that will fail if
|
||||
// we are restricted.
|
||||
if (!ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
|
||||
// It's better to log and show an error message than to invite user
|
||||
// confusion by removing about:accounts entirely. That is, if the user is
|
||||
// restricted, this way they'll discover as much and may be able to get
|
||||
// out of their restricted profile. If we remove about:accounts entirely,
|
||||
// it will look like Fennec is buggy, and the user will be very confused.
|
||||
log.e("This profile cannot connect to Firefox Accounts: showing restricted error.");
|
||||
show("restrictedError");
|
||||
return;
|
||||
}
|
||||
|
||||
Accounts.getFirefoxAccount().then(user => {
|
||||
// It's possible for the window to start closing before getting the user
|
||||
// completes. Tests in particular can cause this.
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDisplayedEmail(user);
|
||||
|
||||
// Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
|
||||
// searchParams is empty.
|
||||
let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
|
||||
let action = urlParams.get(ACTION_URL_PARAM);
|
||||
urlParams.delete(ACTION_URL_PARAM);
|
||||
|
||||
switch (action) {
|
||||
case "signup":
|
||||
if (user) {
|
||||
// Asking to sign-up when already signed in just shows prefs.
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
case "signin":
|
||||
if (user) {
|
||||
// Asking to sign-in when already signed in just shows prefs.
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signin", urlParams));
|
||||
}
|
||||
break;
|
||||
case "force_auth":
|
||||
if (user) {
|
||||
show("spinner");
|
||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
||||
wrapper.init(getURLForAction("force_auth", urlParams));
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
case "manage":
|
||||
if (user) {
|
||||
show("spinner");
|
||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
||||
wrapper.init(getURLForAction("settings", urlParams));
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Unrecognized or no action specified.
|
||||
if (action) {
|
||||
log.w("Ignoring unrecognized action: " + action);
|
||||
}
|
||||
if (user) {
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}).catch(e => {
|
||||
log.e("Failed to get the signed in user: " + e.toString());
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function onload() {
|
||||
document.removeEventListener("DOMContentLoaded", onload, true);
|
||||
init();
|
||||
var buttonRetry = document.getElementById('buttonRetry');
|
||||
buttonRetry.addEventListener('click', retry);
|
||||
|
||||
var buttonOpenPrefs = document.getElementById('buttonOpenPrefs');
|
||||
buttonOpenPrefs.addEventListener('click', openPrefs);
|
||||
}, true);
|
||||
|
||||
// This window is contained in a XUL <browser> element. Return the
|
||||
// messageManager of that <browser> element, or null.
|
||||
function getBrowserMessageManager() {
|
||||
let browser = window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow)
|
||||
.BrowserApp
|
||||
.getBrowserForDocument(document);
|
||||
if (browser) {
|
||||
return browser.messageManager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add a single listener for 'loaded' messages from the iframe in this
|
||||
// <browser>. These 'loaded' messages are ferried from the WebChannel to just
|
||||
// this <browser>.
|
||||
let mm = getBrowserMessageManager();
|
||||
if (mm) {
|
||||
mm.addMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
||||
} else {
|
||||
log.e('No messageManager, not listening for LOADED message!');
|
||||
}
|
||||
|
||||
window.addEventListener("unload", function(event) {
|
||||
try {
|
||||
let mm = getBrowserMessageManager();
|
||||
if (mm) {
|
||||
mm.removeMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
// This could fail if the page is being torn down, the tab is being
|
||||
// destroyed, etc.
|
||||
log.w('Not removing listener for LOADED message: ' + e.toString());
|
||||
}
|
||||
});
|
83
mobile/android/chrome/content/aboutAccounts.xhtml
Normal file
83
mobile/android/chrome/content/aboutAccounts.xhtml
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-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/. -->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
|
||||
%aboutDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
|
||||
<head>
|
||||
<title>Firefox Sync</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutAccounts.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="spinner" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<!-- Empty text-container for spacing. -->
|
||||
<div class="text-container flex-column" />
|
||||
|
||||
<div class="mui-refresh-main">
|
||||
<div class="mui-refresh-wrapper">
|
||||
<div class="mui-spinner-wrapper">
|
||||
<div class="mui-spinner-main">
|
||||
<div class="mui-spinner-left">
|
||||
<div class="mui-half-circle-left" />
|
||||
</div>
|
||||
<div class="mui-spinner-right">
|
||||
<div class="mui-half-circle-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe mozframetype="content" id="remote" class="toplevel" />
|
||||
|
||||
<div id="prefs" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.connected.title;</div>
|
||||
<div class="hint">&aboutAccounts.connected.description;</div>
|
||||
<div id="email" class="hint"></div>
|
||||
</div>
|
||||
<a id="buttonOpenPrefs" tabindex="0" href="#">&aboutAccounts.syncPreferences.label;</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="networkError" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.noConnection.title;</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button id="buttonRetry" class="button" tabindex="1">&aboutAccounts.retry.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="restrictedError" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.restrictedError.title;</div>
|
||||
<div class="hint">&aboutAccounts.restrictedError.description;</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutAccounts.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -16,6 +16,7 @@
|
||||
<title>&aboutLogins.title;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutLogins.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutLogins.js"></script>
|
||||
@ -38,7 +39,22 @@
|
||||
</div>
|
||||
<div id="logins-list-loading-body" class="hidden">
|
||||
<div id="loading-img-container">
|
||||
<object type="image/svg+xml" id="spinner" data="chrome://browser/skin/images/spinning_throbber.svg"/>
|
||||
|
||||
<div id="spinner" class="mui-refresh-main">
|
||||
<div class="mui-refresh-wrapper">
|
||||
<div class="mui-spinner-wrapper">
|
||||
<div class="mui-spinner-main">
|
||||
<div class="mui-spinner-left">
|
||||
<div class="mui-half-circle-left" />
|
||||
</div>
|
||||
<div class="mui-spinner-right">
|
||||
<div class="mui-half-circle-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="empty-body" class="hidden">
|
||||
|
@ -555,9 +555,15 @@ var BrowserApp = {
|
||||
// about:accounts, which can happen when starting the Firefox Account flow
|
||||
// from the first run experience, or via the Firefox Account Status
|
||||
// Activity, we can and do miss messages from the fxa-content-server.
|
||||
console.log("browser.js: loading Firefox Accounts WebChannel");
|
||||
Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm");
|
||||
EnsureFxAccountsWebChannel();
|
||||
// However, we never allow suitably restricted profiles from listening to
|
||||
// fxa-content-server messages.
|
||||
if (ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
|
||||
console.log("browser.js: loading Firefox Accounts WebChannel");
|
||||
Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm");
|
||||
EnsureFxAccountsWebChannel();
|
||||
} else {
|
||||
console.log("browser.js: not loading Firefox Accounts WebChannel; this profile cannot connect to Firefox Accounts.");
|
||||
}
|
||||
}
|
||||
|
||||
// Notify Java that Gecko has loaded.
|
||||
|
@ -59,6 +59,10 @@ chrome.jar:
|
||||
#ifdef MOZ_DEVICES
|
||||
content/aboutDevices.xhtml (content/aboutDevices.xhtml)
|
||||
content/aboutDevices.js (content/aboutDevices.js)
|
||||
#endif
|
||||
#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI
|
||||
content/aboutAccounts.xhtml (content/aboutAccounts.xhtml)
|
||||
content/aboutAccounts.js (content/aboutAccounts.js)
|
||||
#endif
|
||||
content/aboutLogins.xhtml (content/aboutLogins.xhtml)
|
||||
content/aboutLogins.js (content/aboutLogins.js)
|
||||
|
@ -89,6 +89,12 @@ if (AppConstants.MOZ_DEVICES) {
|
||||
privileged: true
|
||||
};
|
||||
}
|
||||
if (!AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) {
|
||||
modules['accounts'] = {
|
||||
uri: "chrome://browser/content/aboutAccounts.xhtml",
|
||||
privileged: true
|
||||
};
|
||||
}
|
||||
|
||||
function AboutRedirector() {}
|
||||
AboutRedirector.prototype = {
|
||||
|
@ -20,7 +20,9 @@ contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-
|
||||
#ifdef MOZ_DEVICES
|
||||
contract @mozilla.org/network/protocol/about;1?what=devices {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
#endif
|
||||
|
||||
#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI
|
||||
contract @mozilla.org/network/protocol/about;1?what=accounts {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
#endif
|
||||
contract @mozilla.org/network/protocol/about;1?what=logins {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
|
||||
# DirectoryProvider.js
|
||||
|
13
mobile/android/locales/en-US/chrome/aboutAccounts.dtd
Normal file
13
mobile/android/locales/en-US/chrome/aboutAccounts.dtd
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY aboutAccounts.connected.title "Firefox Accounts">
|
||||
<!ENTITY aboutAccounts.connected.description "You are connected as">
|
||||
<!ENTITY aboutAccounts.syncPreferences.label "Tap here to check Sync settings">
|
||||
|
||||
<!ENTITY aboutAccounts.noConnection.title "No Internet connection">
|
||||
<!ENTITY aboutAccounts.retry.label "Try again">
|
||||
|
||||
<!ENTITY aboutAccounts.restrictedError.title "Restricted">
|
||||
<!ENTITY aboutAccounts.restrictedError.description "You cannot manage Firefox Accounts from this profile.">
|
@ -8,6 +8,7 @@
|
||||
% locale browser @AB_CD@ %locale/@AB_CD@/browser/
|
||||
locale/@AB_CD@/browser/about.dtd (%chrome/about.dtd)
|
||||
#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI
|
||||
locale/@AB_CD@/browser/aboutAccounts.dtd (%chrome/aboutAccounts.dtd)
|
||||
locale/@AB_CD@/browser/aboutAccounts.properties (%chrome/aboutAccounts.properties)
|
||||
#endif
|
||||
locale/@AB_CD@/browser/aboutAddons.dtd (%chrome/aboutAddons.dtd)
|
||||
|
91
mobile/android/themes/core/aboutAccounts.css
Normal file
91
mobile/android/themes/core/aboutAccounts.css
Normal file
@ -0,0 +1,91 @@
|
||||
/* 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/. */
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div {
|
||||
transition: opacity 0.4s ease-in;
|
||||
}
|
||||
|
||||
#spinner {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
|
||||
#remote {
|
||||
border: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease-in;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #363B40;
|
||||
font-size: 25px;
|
||||
font-weight: lighter;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #777777;
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0096DD; /* link_blue */
|
||||
text-decoration: none;
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #0082C6; /* link_blue_pressed */
|
||||
}
|
||||
|
||||
.toplevel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
padding-top: 60px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
flex: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
background-color: #E66000; /*matched to action_orange in java codebase*/
|
||||
color: #FFFFFF;
|
||||
font-size: 20px;
|
||||
border-radius: 4px;
|
||||
border-width: 0px;
|
||||
}
|
@ -200,8 +200,6 @@ body {
|
||||
|
||||
#spinner {
|
||||
margin-top: 60px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#empty-body {
|
||||
|
@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- 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 class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css">
|
||||
.spinner {
|
||||
animation: rotator 1.4s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotator {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
|
||||
.path {
|
||||
stroke-dasharray: 187;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes colors {
|
||||
0% {
|
||||
stroke: #FF9500;
|
||||
}
|
||||
25% {
|
||||
stroke: #FF9500;
|
||||
}
|
||||
50% {
|
||||
stroke: #FF9500;
|
||||
}
|
||||
75% {
|
||||
stroke: #FF9500;
|
||||
}
|
||||
100% {
|
||||
stroke: #FF9500;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dashoffset: 187;
|
||||
}
|
||||
50% {
|
||||
stroke-dashoffset: 46.75;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 187;
|
||||
transform: rotate(450deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
@ -8,6 +8,9 @@ chrome.jar:
|
||||
% skin browser classic/1.0 %skin/
|
||||
skin/aboutPage.css (aboutPage.css)
|
||||
skin/about.css (about.css)
|
||||
#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI
|
||||
skin/aboutAccounts.css (aboutAccounts.css)
|
||||
#endif
|
||||
* skin/aboutAddons.css (aboutAddons.css)
|
||||
* skin/aboutBase.css (aboutBase.css)
|
||||
#ifdef MOZ_DEVICES
|
||||
@ -29,6 +32,7 @@ chrome.jar:
|
||||
skin/config.css (config.css)
|
||||
skin/touchcontrols.css (touchcontrols.css)
|
||||
skin/netError.css (netError.css)
|
||||
skin/spinner.css (spinner.css)
|
||||
% override chrome://global/skin/about.css chrome://browser/skin/about.css
|
||||
% override chrome://global/skin/aboutMemory.css chrome://browser/skin/aboutMemory.css
|
||||
% override chrome://global/skin/aboutReader.css chrome://browser/skin/aboutReader.css
|
||||
@ -44,7 +48,6 @@ chrome.jar:
|
||||
#
|
||||
|
||||
* skin/aboutLogins.css (aboutLogins.css)
|
||||
skin/images/spinning_throbber.svg (images/spinning_throbber.svg)
|
||||
|
||||
skin/images/search.png (images/search.png)
|
||||
skin/images/lock.png (images/lock.png)
|
||||
|
124
mobile/android/themes/core/spinner.css
Normal file
124
mobile/android/themes/core/spinner.css
Normal file
@ -0,0 +1,124 @@
|
||||
/* 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/. */
|
||||
|
||||
.mui-refresh-main {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mui-refresh-wrapper {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.mui-spinner-main {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
animation: sporadic-rotate 5.25s cubic-bezier(.35, 0, .25, 1) infinite;
|
||||
}
|
||||
|
||||
.mui-spinner-wrapper {
|
||||
animation: outer-rotate 2.91667s linear infinite;
|
||||
}
|
||||
|
||||
.mui-spinner-left, .mui-spinner-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 60px;
|
||||
width: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mui-spinner-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.mui-spinner-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mui-half-circle-left, .mui-half-circle-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #000 #000 transparent;
|
||||
border-radius: 999px;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 1.3125s;
|
||||
animation-timing-function: cubic-bezier(.35, 0, .25, 1);
|
||||
}
|
||||
|
||||
.mui-half-circle-left {
|
||||
left: 0;
|
||||
border-right-color: transparent;
|
||||
border-top-color: #FF9500; /*matched to fennec_ui_orange in java codebase*/
|
||||
border-left-color: #FF9500; /*matched to fennec_ui_orange in java codebase*/
|
||||
animation-name: left-wobble;
|
||||
}
|
||||
|
||||
.mui-half-circle-right {
|
||||
right: 0;
|
||||
border-left-color: transparent;
|
||||
border-top-color: #FF9500; /*matched to fennec_ui_orange in java codebase*/
|
||||
border-right-color: #FF9500; /*matched to fennec_ui_orange in java codebase*/
|
||||
animation-name: right-wobble;
|
||||
}
|
||||
|
||||
@keyframes outer-rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes left-wobble {
|
||||
0%, 100% {
|
||||
transform: rotate(130deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes right-wobble {
|
||||
0%, 100% {
|
||||
transform: rotate(-130deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sporadic-rotate {
|
||||
12.5% {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
37.5% {
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(540deg);
|
||||
}
|
||||
62.5% {
|
||||
transform: rotate(675deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(810deg);
|
||||
}
|
||||
87.5% {
|
||||
transform: rotate(945deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(1080deg);
|
||||
}
|
||||
}
|
@ -8176,7 +8176,7 @@
|
||||
},
|
||||
"LOOP_SHARING_ROOM_URL": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"expires_in_version": "45",
|
||||
"kind": "enumerated",
|
||||
"n_values": 8,
|
||||
"releaseChannelCollection": "opt-out",
|
||||
@ -8184,7 +8184,7 @@
|
||||
},
|
||||
"LOOP_ROOM_CREATE": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"expires_in_version": "45",
|
||||
"kind": "enumerated",
|
||||
"n_values": 4,
|
||||
"releaseChannelCollection": "opt-out",
|
||||
@ -8192,7 +8192,7 @@
|
||||
},
|
||||
"LOOP_ROOM_DELETE": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"expires_in_version": "45",
|
||||
"kind": "enumerated",
|
||||
"n_values": 4,
|
||||
"releaseChannelCollection": "opt-out",
|
||||
@ -8200,7 +8200,7 @@
|
||||
},
|
||||
"LOOP_ROOM_CONTEXT_ADD": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"expires_in_version": "45",
|
||||
"kind": "enumerated",
|
||||
"n_values": 8,
|
||||
"releaseChannelCollection": "opt-out",
|
||||
@ -8208,7 +8208,7 @@
|
||||
},
|
||||
"LOOP_ROOM_CONTEXT_CLICK": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
|
||||
"expires_in_version": "44",
|
||||
"expires_in_version": "45",
|
||||
"kind": "count",
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Number times room context is clicked to visit the attached URL"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user