From 977514b893fb92a737efa787ff0e26a040e99304 Mon Sep 17 00:00:00 2001 From: "Luis Miguel [:quicksaver]" Date: Wed, 4 Nov 2015 15:26:40 +0000 Subject: [PATCH 01/60] Bug 1218351 - (e10s) Don't lose initial typed characters when opening the findbar. r=mikedeboer --- browser/base/content/tabbrowser.xml | 69 ++++++++++++++----- .../BrowserTestUtils/BrowserTestUtils.jsm | 13 +++- .../tests/SimpleTest/AsyncUtilsContent.js | 5 +- toolkit/content/browser-content.js | 38 +++++----- .../content/tests/browser/browser_findbar.js | 39 +++++++++++ toolkit/content/widgets/findbar.xml | 23 +++++-- toolkit/modules/BrowserUtils.jsm | 20 +++++- 7 files changed, 159 insertions(+), 48 deletions(-) diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index f457da2d139..a025ceb7c56 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -4111,13 +4111,29 @@ browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned }) break; } - case "Findbar:Keypress": - if (!gFindBarInitialized) { - // If the find bar for this tab is not yet alive, change that, - // and make sure we return the result: - return gFindBar.receiveMessage(aMessage); + case "Findbar:Keypress": { + let tab = this.getTabForBrowser(browser); + // If the find bar for this tab is not yet alive, only initialize + // it if there's a possibility FindAsYouType will be used. + // There's no point in doing it for most random keypresses. + if (!this.isFindBarInitialized(tab) && + aMessage.data.shouldFastFind) { + let shouldFastFind = this._findAsYouType; + if (!shouldFastFind) { + // Please keep in sync with toolkit/content/widgets/findbar.xml + const FAYT_LINKS_KEY = "'"; + const FAYT_TEXT_KEY = "/"; + let charCode = aMessage.data.fakeEvent.charCode; + let key = charCode ? String.fromCharCode(charCode) : null; + shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY; + } + if (shouldFastFind) { + // Make sure we return the result. + return this.getFindBar(tab).receiveMessage(aMessage); + } } break; + } } ]]> @@ -4128,22 +4144,30 @@ @@ -4219,6 +4243,11 @@ } messageManager.addMessageListener("DOMWebNotificationClicked", this); messageManager.addMessageListener("DOMServiceWorkerFocusClient", this); + + // To correctly handle keypresses for potential FindAsYouType, while + // the tab's find bar is not yet initialized. + this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind"); + Services.prefs.addObserver("accessibility.typeaheadfind", this, false); messageManager.addMessageListener("Findbar:Keypress", this); ]]> @@ -4280,6 +4309,8 @@ this._switcher.destroy(); } } + + Services.prefs.removeObserver("accessibility.typeaheadfind", this); ]]> diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm index 6212faa1dd0..11881296a6f 100644 --- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm +++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm @@ -32,6 +32,8 @@ Cc["@mozilla.org/globalmessagemanager;1"] XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils", "resource:///modules/E10SUtils.jsm"); +var gSendCharCount = 0; + this.BrowserTestUtils = { /** * Loads a page in a new tab, executes a Task and closes the tab. @@ -724,12 +726,21 @@ this.BrowserTestUtils = { */ sendChar(char, browser) { return new Promise(resolve => { + let seq = ++gSendCharCount; let mm = browser.messageManager; + mm.addMessageListener("Test:SendCharDone", function charMsg(message) { + if (message.data.seq != seq) + return; + mm.removeMessageListener("Test:SendCharDone", charMsg); resolve(message.data.sendCharResult); }); - mm.sendAsyncMessage("Test:SendChar", { char: char }); + + mm.sendAsyncMessage("Test:SendChar", { + char: char, + seq: seq + }); }); } }; diff --git a/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js index 92af2d77418..793e2385808 100644 --- a/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js +++ b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js @@ -53,5 +53,8 @@ addMessageListener("Test:SynthesizeMouse", (message) => { addMessageListener("Test:SendChar", message => { let result = EventUtils.sendChar(message.data.char, content); - sendAsyncMessage("Test:SendCharDone", { sendCharResult: result }); + sendAsyncMessage("Test:SendCharDone", { + sendCharResult: result, + seq: message.data.seq + }); }); diff --git a/toolkit/content/browser-content.js b/toolkit/content/browser-content.js index 7cffc898926..0bb40078012 100644 --- a/toolkit/content/browser-content.js +++ b/toolkit/content/browser-content.js @@ -580,15 +580,10 @@ var FindBar = { FIND_NORMAL: 0, FIND_TYPEAHEAD: 1, FIND_LINKS: 2, - FAYT_LINKS_KEY: "'".charCodeAt(0), - FAYT_TEXT_KEY: "/".charCodeAt(0), _findMode: 0, - _findAsYouType: false, init() { - this._findAsYouType = - Services.prefs.getBoolPref("accessibility.typeaheadfind"); addMessageListener("Findbar:UpdateState", this); Services.els.addSystemEventListener(global, "keypress", this, false); Services.els.addSystemEventListener(global, "mouseup", this, false); @@ -598,7 +593,6 @@ var FindBar = { switch (msg.name) { case "Findbar:UpdateState": this._findMode = msg.data.findMode; - this._findAsYouType = msg.data.findAsYouType; break; } }, @@ -618,13 +612,18 @@ var FindBar = { * Returns whether FAYT can be used for the given event in * the current content state. */ - _shouldFastFind() { - //XXXgijs: why all these shenanigans? Why not use the event's target? - let focusedWindow = {}; - let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow); - let win = focusedWindow.value; + _canAndShouldFastFind() { let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}); - return BrowserUtils.shouldFastFind(elt, win); + let should = false; + let can = BrowserUtils.canFastFind(content); + if (can) { + //XXXgijs: why all these shenanigans? Why not use the event's target? + let focusedWindow = {}; + let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow); + let win = focusedWindow.value; + should = BrowserUtils.shouldFastFind(elt, win); + } + return { can, should } }, _onKeypress(event) { @@ -632,14 +631,12 @@ var FindBar = { if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) { return; } - // Not interested in random keypresses most of the time: - if (this._findMode == this.FIND_NORMAL && !this._findAsYouType && - event.charCode != this.FAYT_LINKS_KEY && event.charCode != this.FAYT_TEXT_KEY) { - return; - } // Check the focused element etc. - if (!this._shouldFastFind()) { + let fastFind = this._canAndShouldFastFind(); + + // Can we even use find in this page at all? + if (!fastFind.can) { return; } @@ -651,7 +648,10 @@ var FindBar = { } } // sendSyncMessage returns an array of the responses from all listeners - let rv = sendSyncMessage("Findbar:Keypress", fakeEvent); + let rv = sendSyncMessage("Findbar:Keypress", { + fakeEvent: fakeEvent, + shouldFastFind: fastFind.should + }); if (rv.indexOf(false) !== -1) { event.preventDefault(); return false; diff --git a/toolkit/content/tests/browser/browser_findbar.js b/toolkit/content/tests/browser/browser_findbar.js index 0d08e4b4139..e438eb55b6d 100644 --- a/toolkit/content/tests/browser/browser_findbar.js +++ b/toolkit/content/tests/browser/browser_findbar.js @@ -158,6 +158,45 @@ add_task(function * test_reinitialization_at_remoteness_change() { yield BrowserTestUtils.removeTab(tab); }); +/** + * Ensure that the initial typed characters aren't lost immediately after + * opening the find bar. + */ +add_task(function* () { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return true; + } + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let browser = tab.linkedBrowser; + + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + let findBar = gFindBar; + let initialValue = findBar._findField.value; + + EventUtils.synthesizeKey("f", { accelKey: true }, window); + + let promises = [ + BrowserTestUtils.sendChar("a", browser), + BrowserTestUtils.sendChar("b", browser), + BrowserTestUtils.sendChar("c", browser) + ]; + + isnot(document.activeElement, findBar._findField.inputField, + "findbar is not yet focused"); + is(findBar._findField.value, initialValue, "still has initial find query"); + + yield Promise.all(promises); + is(document.activeElement, findBar._findField.inputField, + "findbar is now focused"); + is(findBar._findField.value, "abc", "abc fully entered as find query"); + + yield BrowserTestUtils.removeTab(tab); +}); + function promiseFindFinished(searchText, highlightOn) { let deferred = Promise.defer(); diff --git a/toolkit/content/widgets/findbar.xml b/toolkit/content/widgets/findbar.xml index 296c0d7b2cc..7567890a5d7 100644 --- a/toolkit/content/widgets/findbar.xml +++ b/toolkit/content/widgets/findbar.xml @@ -283,7 +283,7 @@ this._browser = val; if (this._browser) { - // Need to do this in case FAYT + // Need to do this to ensure the correct initial state. this._updateBrowserWithState(); this._browser.messageManager.addMessageListener("Findbar:Keypress", this); this._browser.messageManager.addMessageListener("Findbar:Mouseup", this); @@ -318,7 +318,6 @@ switch (aPrefName) { case "accessibility.typeaheadfind": this._self._findAsYouType = prefsvc.getBoolPref(aPrefName); - this._self._updateBrowserWithState(); break; case "accessibility.typeaheadfind.linksonly": this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName); @@ -739,10 +738,21 @@ on the real event. --> + @@ -804,8 +818,7 @@ diff --git a/toolkit/modules/BrowserUtils.jsm b/toolkit/modules/BrowserUtils.jsm index 6a70c51ac9c..5238e150877 100644 --- a/toolkit/modules/BrowserUtils.jsm +++ b/toolkit/modules/BrowserUtils.jsm @@ -247,7 +247,7 @@ this.BrowserUtils = { }, /** - * Return true if we can/should FAYT for this node + window (could be CPOW): + * Return true if we should FAYT for this node + window (could be CPOW): * * @param elt * The element that is focused @@ -270,7 +270,21 @@ this.BrowserUtils = { return false; } - if (win && !this.mimeTypeIsTextBased(win.document.contentType)) + return true; + }, + + /** + * Return true if we can FAYT for this window (could be CPOW): + * + * @param win + * The top level window that is focused + * + */ + canFastFind: function(win) { + if (!win) + return false; + + if (!this.mimeTypeIsTextBased(win.document.contentType)) return false; // disable FAYT in about:blank to prevent FAYT opening unexpectedly. @@ -280,7 +294,7 @@ this.BrowserUtils = { // disable FAYT in documents that ask for it to be disabled. if ((loc.protocol == "about:" || loc.protocol == "chrome:") && - (win && win.document.documentElement && + (win.document.documentElement && win.document.documentElement.getAttribute("disablefastfind") == "true")) return false; From 4c89e07c612a2aa7a9ef50c4e3e42a0dc972cfa4 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Fri, 15 Jan 2016 13:04:39 +1100 Subject: [PATCH 02/60] Bug 1218351 - Make fullscreen-api-keys test not affected by other chrome message listener. r=smaug --- dom/html/test/browser_fullscreen-api-keys.js | 34 ++++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/dom/html/test/browser_fullscreen-api-keys.js b/dom/html/test/browser_fullscreen-api-keys.js index ef55ced6cca..f34294d8137 100644 --- a/dom/html/test/browser_fullscreen-api-keys.js +++ b/dom/html/test/browser_fullscreen-api-keys.js @@ -82,16 +82,21 @@ function* temporaryRemoveUnexpectedKeyEventCapture(callback) { "Test:KeyReceived", captureUnexpectedKeyEvent); } -function* receiveExpectedKeyEvents(keyCode) { - info("Waiting for key events"); - let events = ["keydown", "keypress", "keyup"]; - while (events.length > 0) { - let evt = yield promiseOneMessage("Test:KeyReceived"); - let expected = events.shift(); - is(evt.type, expected, `Should receive a ${expected} event`); - is(evt.keyCode, keyCode, - `Should receive the event with key code ${keyCode}`); - } +function receiveExpectedKeyEvents(keyCode) { + return new Promise(resolve => { + let events = ["keydown", "keypress", "keyup"]; + function listener({ data }) { + let expected = events.shift(); + is(data.type, expected, `Should receive a ${expected} event`); + is(data.keyCode, keyCode, + `Should receive the event with key code ${keyCode}`); + if (!events.length) { + gMessageManager.removeMessageListener("Test:KeyReceived", listener); + resolve(); + } + } + gMessageManager.addMessageListener("Test:KeyReceived", listener); + }); } const kPage = "http://example.org/browser/" + @@ -143,17 +148,18 @@ add_task(function* () { info("Dispatch untrusted key events from content"); yield* temporaryRemoveUnexpectedKeyEventCapture(function* () { + let promiseExpectedKeyEvents = receiveExpectedKeyEvents(keyCode); gMessageManager.sendAsyncMessage("Test:DispatchUntrustedKeyEvents", code); - yield* receiveExpectedKeyEvents(keyCode); + yield promiseExpectedKeyEvents; }); info("Send trusted key events"); yield* temporaryRemoveUnexpectedFullscreenChangeCapture(function* () { yield* temporaryRemoveUnexpectedKeyEventCapture(function* () { + let promiseExpectedKeyEvents = suppressed ? + Promise.resolve() : receiveExpectedKeyEvents(keyCode); EventUtils.synthesizeKey(code, {}); - if (!suppressed) { - yield* receiveExpectedKeyEvents(keyCode); - } + yield promiseExpectedKeyEvents; let state = yield promiseOneMessage("Test:FullscreenChanged"); ok(!state, "The content should have exited fullscreen"); ok(!document.mozFullScreenElement, From 74e180e2979ca60efc47922be1233cdc0de46c43 Mon Sep 17 00:00:00 2001 From: Daniel Vucci Date: Mon, 18 Jan 2016 11:40:00 +0100 Subject: [PATCH 03/60] Bug 1236864 - Create RobotiumHelper.waitForExactText() and RobotiumHelper.searchExactText(). r=sebastian --- .../tests/components/AppMenuComponent.java | 7 +-- .../tests/helpers/HelperInitializer.java | 1 + .../gecko/tests/helpers/RobotiumHelper.java | 43 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/RobotiumHelper.java diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java index 17ccc1e4d04..4b486148d15 100644 --- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/AppMenuComponent.java @@ -17,6 +17,7 @@ import org.mozilla.gecko.menu.MenuItemActionBar; import org.mozilla.gecko.menu.MenuItemDefault; import org.mozilla.gecko.tests.UITestContext; import org.mozilla.gecko.tests.helpers.DeviceHelper; +import org.mozilla.gecko.tests.helpers.RobotiumHelper; import org.mozilla.gecko.tests.helpers.WaitHelper; import android.text.TextUtils; @@ -159,7 +160,7 @@ public class AppMenuComponent extends BaseComponent { * This method is dependent on not having two views with equivalent contentDescription / text. */ private View findAppMenuItemView(String text) { - mSolo.waitForText(String.format("^%s$", text), 1, MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS); + RobotiumHelper.waitForExactText(text, 1, MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS); final List views = mSolo.getViews(); @@ -299,7 +300,7 @@ public class AppMenuComponent extends BaseComponent { private boolean isLegacyMoreMenuOpen() { // Check if the first menu option is visible. final String shareTitle = mSolo.getString(R.string.share); - return mSolo.searchText(String.format("^%s$", shareTitle), true); + return RobotiumHelper.searchExactText(shareTitle, true); } /** @@ -311,7 +312,7 @@ public class AppMenuComponent extends BaseComponent { */ private boolean isMenuOpen(String menuItemTitle) { final View menuItemView = findAppMenuItemView(menuItemTitle); - return isMenuOpen(menuItemView) ? true : mSolo.searchText(String.format("^%s$", menuItemTitle), true); + return isMenuOpen(menuItemView) ? true : RobotiumHelper.searchExactText(menuItemTitle, true); } /** diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/HelperInitializer.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/HelperInitializer.java index a4a1fec2e56..229dc106244 100644 --- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/HelperInitializer.java +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/HelperInitializer.java @@ -24,6 +24,7 @@ public final class HelperInitializer { GeckoHelper.init(context); JavascriptBridge.init(context); NavigationHelper.init(context); + RobotiumHelper.init(context); WaitHelper.init(context); } } diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/RobotiumHelper.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/RobotiumHelper.java new file mode 100644 index 00000000000..5061e31cf3f --- /dev/null +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/helpers/RobotiumHelper.java @@ -0,0 +1,43 @@ +/* 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.tests.helpers; + +import com.jayway.android.robotium.solo.Solo; + +import org.mozilla.gecko.tests.UITestContext; + +import java.util.regex.Pattern; + +/** + * Provides helper functions for using Robotium. + */ +public final class RobotiumHelper { + private static Solo sSolo; + + private RobotiumHelper() { /* To disallow instantiation. */ } + + protected static void init(final UITestContext context) { + sSolo = context.getSolo(); + } + + /** + * Same as Solo.waitForText(), but matching against full text, without regular expressions. + */ + public static boolean waitForExactText(final String text, + final int minimumNumberOfMatches, + final long timeout) { + String matchText = "^" + Pattern.quote(text) + "$"; + return sSolo.waitForText(matchText, minimumNumberOfMatches, timeout); + } + + /** + * Same as Solo.searchText(), but matching against full text, without regular expressions. + */ + public static boolean searchExactText(final String text, + final boolean onlyVisible) { + String matchText = "^" + Pattern.quote(text) + "$"; + return sSolo.searchText(matchText, onlyVisible); + } +} From ebadbcc0f55fb6642afcdbc438697f8742040b41 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Mon, 18 Jan 2016 22:37:37 +0100 Subject: [PATCH 04/60] Bug 1240368 - skip intermittent performance test on linux e10s. r=vporof --- devtools/client/performance/test/browser.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/client/performance/test/browser.ini b/devtools/client/performance/test/browser.ini index d2082aab334..c272ba94a3c 100644 --- a/devtools/client/performance/test/browser.ini +++ b/devtools/client/performance/test/browser.ini @@ -127,6 +127,7 @@ skip-if = os == 'linux' || debug # bug 1186322 for Linux, bug 1203895 for leaks [browser_perf-theme-toggle-01.js] [browser_perf-telemetry.js] [browser_profiler_tree-abstract-01.js] +skip-if = e10s && os == 'linux' # bug 1186322 [browser_profiler_tree-abstract-02.js] [browser_profiler_tree-abstract-03.js] [browser_profiler_tree-abstract-04.js] From 5082deee548163de9f588f6aaefa57c111b320f6 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Mon, 18 Jan 2016 16:24:36 +0100 Subject: [PATCH 05/60] Bug 1239730 - memory table formatNumber/Percent : add space every 3 digits. r=jsantell --- .../memory/components/census-tree-item.js | 37 +++++-------------- .../memory/components/dominator-tree-item.js | 24 +++--------- .../client/memory/test/unit/test_utils.js | 27 +++++++++++++- devtools/client/memory/utils.js | 36 ++++++++++++++++++ 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/devtools/client/memory/components/census-tree-item.js b/devtools/client/memory/components/census-tree-item.js index 0beb5c3d69e..77b106a24c5 100644 --- a/devtools/client/memory/components/census-tree-item.js +++ b/devtools/client/memory/components/census-tree-item.js @@ -4,7 +4,7 @@ const { isSavedFrame } = require("devtools/shared/DevToolsUtils"); const { DOM: dom, createClass, createFactory } = require("devtools/client/shared/vendor/react"); -const { L10N } = require("../utils"); +const { L10N, formatNumber, formatPercent } = require("../utils"); const Frame = createFactory(require("devtools/client/shared/components/frame")); const unknownSourceString = L10N.getStr("unknownSource"); const { TREE_ROW_HEIGHT } = require("../constants"); @@ -12,25 +12,6 @@ const { TREE_ROW_HEIGHT } = require("../constants"); const CensusTreeItem = module.exports = createClass({ displayName: "CensusTreeItem", - formatPercent(showSign, percent) { - return L10N.getFormatStr("tree-item.percent", - this.formatNumber(showSign, percent)); - }, - - formatNumber(showSign, number) { - const rounded = Math.round(number); - if (rounded === 0 || rounded === -0) { - return "0"; - } - - let sign = ""; - if (showSign) { - sign = rounded < 0 ? "-" : "+"; - } - - return sign + Math.abs(rounded); - }, - shouldComponentUpdate(nextProps, nextState) { return this.props.item != nextProps.item || this.props.depth != nextProps.depth @@ -52,17 +33,17 @@ const CensusTreeItem = module.exports = createClass({ onViewSourceInDebugger, } = this.props; - const bytes = this.formatNumber(showSign, item.bytes); - const percentBytes = this.formatPercent(showSign, getPercentBytes(item.bytes)); + const bytes = formatNumber(item.bytes, showSign); + const percentBytes = formatPercent(getPercentBytes(item.bytes), showSign); - const count = this.formatNumber(showSign, item.count); - const percentCount = this.formatPercent(showSign, getPercentCount(item.count)); + const count = formatNumber(item.count, showSign); + const percentCount = formatPercent(getPercentCount(item.count), showSign); - const totalBytes = this.formatNumber(showSign, item.totalBytes); - const percentTotalBytes = this.formatPercent(showSign, getPercentBytes(item.totalBytes)); + const totalBytes = formatNumber(item.totalBytes, showSign); + const percentTotalBytes = formatPercent(getPercentBytes(item.totalBytes), showSign); - const totalCount = this.formatNumber(showSign, item.totalCount); - const percentTotalCount = this.formatPercent(showSign, getPercentCount(item.totalCount)); + const totalCount = formatNumber(item.totalCount, showSign); + const percentTotalCount = formatPercent(getPercentCount(item.totalCount), showSign); return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` }, dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }, diff --git a/devtools/client/memory/components/dominator-tree-item.js b/devtools/client/memory/components/dominator-tree-item.js index b8fb3306272..f69780c3735 100644 --- a/devtools/client/memory/components/dominator-tree-item.js +++ b/devtools/client/memory/components/dominator-tree-item.js @@ -4,7 +4,7 @@ const { assert, isSavedFrame } = require("devtools/shared/DevToolsUtils"); const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); -const { L10N } = require("../utils"); +const { L10N, formatNumber, formatPercent } = require("../utils"); const Frame = createFactory(require("devtools/client/shared/components/frame")); const { TREE_ROW_HEIGHT } = require("../constants"); @@ -28,20 +28,6 @@ const DominatorTreeItem = module.exports = createClass({ onViewSourceInDebugger: PropTypes.func.isRequired, }, - formatPercent(percent) { - return L10N.getFormatStr("tree-item.percent", - this.formatNumber(percent)); - }, - - formatNumber(number) { - const rounded = Math.round(number); - if (rounded === 0 || rounded === -0) { - return "0"; - } - - return String(Math.abs(rounded)); - }, - shouldComponentUpdate(nextProps, nextState) { return this.props.item != nextProps.item || this.props.depth != nextProps.depth @@ -58,11 +44,11 @@ const DominatorTreeItem = module.exports = createClass({ onViewSourceInDebugger, } = this.props; - const retainedSize = this.formatNumber(item.retainedSize); - const percentRetainedSize = this.formatPercent(getPercentSize(item.retainedSize)); + const retainedSize = formatNumber(item.retainedSize); + const percentRetainedSize = formatPercent(getPercentSize(item.retainedSize)); - const shallowSize = this.formatNumber(item.shallowSize); - const percentShallowSize = this.formatPercent(getPercentSize(item.shallowSize)); + const shallowSize = formatNumber(item.shallowSize); + const percentShallowSize = formatPercent(getPercentSize(item.shallowSize)); // Build up our label UI as an array of each label piece, which is either a // string or a frame, and separators in between them. diff --git a/devtools/client/memory/test/unit/test_utils.js b/devtools/client/memory/test/unit/test_utils.js index f5eea7850cc..9f26b3b6f0b 100644 --- a/devtools/client/memory/test/unit/test_utils.js +++ b/devtools/client/memory/test/unit/test_utils.js @@ -3,7 +3,8 @@ /** * Tests the task creator `takeSnapshotAndCensus()` for the whole flow of - * taking a snapshot, and its sub-actions. + * taking a snapshot, and its sub-actions. Tests the formatNumber and + * formatPercent methods. */ let utils = require("devtools/client/memory/utils"); @@ -48,4 +49,28 @@ add_task(function *() { ok(utils.breakdownEquals(utils.getCustomBreakdowns()["My Breakdown"], custom), "utils.getCustomBreakdowns() returns custom breakdowns"); + + ok(true, "test formatNumber util functions"); + equal(utils.formatNumber(12), "12", "formatNumber returns 12 for 12"); + + equal(utils.formatNumber(0), "0", "formatNumber returns 0 for 0"); + equal(utils.formatNumber(-0), "0", "formatNumber returns 0 for -0"); + equal(utils.formatNumber(+0), "0", "formatNumber returns 0 for +0"); + + equal(utils.formatNumber(1234567), "1 234 567", + "formatNumber adds a space every 3rd digit"); + equal(utils.formatNumber(12345678), "12 345 678", + "formatNumber adds a space every 3rd digit"); + equal(utils.formatNumber(123456789), "123 456 789", + "formatNumber adds a space every 3rd digit"); + + equal(utils.formatNumber(12, true), "+12", + "formatNumber can display number sign"); + equal(utils.formatNumber(-12, true), "-12", + "formatNumber can display number sign (negative)"); + + ok(true, "test formatPercent util functions"); + equal(utils.formatPercent(12), "12%", "formatPercent returns 12% for 12"); + equal(utils.formatPercent(12345), "12 345%", + "formatPercent returns 12 345% for 12345"); }); diff --git a/devtools/client/memory/utils.js b/devtools/client/memory/utils.js index c9bd86fb20f..5a280d12a24 100644 --- a/devtools/client/memory/utils.js +++ b/devtools/client/memory/utils.js @@ -517,6 +517,42 @@ exports.openFilePicker = function({ title, filters, defaultName, mode }) { }); }; +/** + * Format the provided number with a space every 3 digits, and optionally + * prefixed by its sign. + * + * @param {Number} number + * @param {Boolean} showSign (defaults to false) + */ +exports.formatNumber = function(number, showSign = false) { + const rounded = Math.round(number); + if (rounded === 0 || rounded === -0) { + return "0"; + } + + const abs = String(Math.abs(rounded)); + // replace every digit followed by (sets of 3 digits) by (itself and a space) + const formatted = abs.replace(/(\d)(?=(\d{3})+$)/g, "$1 "); + + if (showSign) { + const sign = rounded < 0 ? "-" : "+"; + return sign + formatted; + } + return formatted; +}; + +/** + * Format the provided percentage following the same logic as formatNumber and + * an additional % suffix. + * + * @param {Number} percent + * @param {Boolean} showSign (defaults to false) + */ +exports.formatPercent = function(percent, showSign = false) { + return exports.L10N.getFormatStr("tree-item.percent", + exports.formatNumber(percent, showSign)); +}; + /** * Creates a hash map mapping node IDs to its parent node. * From 8633136aa03c867ed59acbc48e42c0e9eb0d3005 Mon Sep 17 00:00:00 2001 From: Jan Odvarko Date: Fri, 15 Jan 2016 14:15:28 +0100 Subject: [PATCH 06/60] Bug 1237253 - Support define in BrowserLoader; r=jlongster r=jryans --- .../client/jsonview/components/headers-panel.js | 2 +- devtools/client/jsonview/components/headers.js | 2 +- devtools/client/jsonview/components/json-panel.js | 2 +- .../client/jsonview/components/main-tabbed-area.js | 2 +- devtools/client/jsonview/components/reps/array.js | 2 +- devtools/client/jsonview/components/reps/caption.js | 2 +- devtools/client/jsonview/components/reps/null.js | 2 +- devtools/client/jsonview/components/reps/number.js | 2 +- .../client/jsonview/components/reps/object-box.js | 2 +- .../client/jsonview/components/reps/object-link.js | 2 +- devtools/client/jsonview/components/reps/object.js | 2 +- .../client/jsonview/components/reps/rep-utils.js | 2 +- devtools/client/jsonview/components/reps/rep.js | 2 +- devtools/client/jsonview/components/reps/string.js | 2 +- devtools/client/jsonview/components/reps/tabs.js | 2 +- devtools/client/jsonview/components/reps/toolbar.js | 2 +- .../client/jsonview/components/reps/tree-view.js | 2 +- .../client/jsonview/components/reps/undefined.js | 2 +- devtools/client/jsonview/components/search-box.js | 2 +- devtools/client/jsonview/components/text-panel.js | 2 +- devtools/client/jsonview/json-viewer.js | 4 +--- devtools/client/jsonview/viewer-config.js | 8 ++++++-- devtools/client/shared/browser-loader.js | 13 +++++++++++++ devtools/client/shared/vendor/react-dom.js | 2 +- 24 files changed, 41 insertions(+), 26 deletions(-) diff --git a/devtools/client/jsonview/components/headers-panel.js b/devtools/client/jsonview/components/headers-panel.js index 11ea0011eb8..30a03c34983 100644 --- a/devtools/client/jsonview/components/headers-panel.js +++ b/devtools/client/jsonview/components/headers-panel.js @@ -6,7 +6,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./reps/rep-utils"); const { Headers } = createFactories(require("./headers")); const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar")); diff --git a/devtools/client/jsonview/components/headers.js b/devtools/client/jsonview/components/headers.js index afe57beee6a..aa4fa3c41eb 100644 --- a/devtools/client/jsonview/components/headers.js +++ b/devtools/client/jsonview/components/headers.js @@ -6,7 +6,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); // Constants const DOM = React.DOM; diff --git a/devtools/client/jsonview/components/json-panel.js b/devtools/client/jsonview/components/json-panel.js index 6d42526f4c0..ebbe974239d 100644 --- a/devtools/client/jsonview/components/json-panel.js +++ b/devtools/client/jsonview/components/json-panel.js @@ -6,7 +6,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./reps/rep-utils"); const { TreeView } = createFactories(require("./reps/tree-view")); const { SearchBox } = createFactories(require("./search-box")); diff --git a/devtools/client/jsonview/components/main-tabbed-area.js b/devtools/client/jsonview/components/main-tabbed-area.js index dd99b7e2402..90bf6fecf50 100644 --- a/devtools/client/jsonview/components/main-tabbed-area.js +++ b/devtools/client/jsonview/components/main-tabbed-area.js @@ -8,7 +8,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./reps/rep-utils"); const { JsonPanel } = createFactories(require("./json-panel")); const { TextPanel } = createFactories(require("./text-panel")); diff --git a/devtools/client/jsonview/components/reps/array.js b/devtools/client/jsonview/components/reps/array.js index 30bca172179..718e6434e73 100644 --- a/devtools/client/jsonview/components/reps/array.js +++ b/devtools/client/jsonview/components/reps/array.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { Rep } = createFactories(require("./rep")); const { ObjectBox } = createFactories(require("./object-box")); diff --git a/devtools/client/jsonview/components/reps/caption.js b/devtools/client/jsonview/components/reps/caption.js index 93000be905e..3bd8dd5788b 100644 --- a/devtools/client/jsonview/components/reps/caption.js +++ b/devtools/client/jsonview/components/reps/caption.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; /** diff --git a/devtools/client/jsonview/components/reps/null.js b/devtools/client/jsonview/components/reps/null.js index c3bad0ac380..84c5cf500dd 100644 --- a/devtools/client/jsonview/components/reps/null.js +++ b/devtools/client/jsonview/components/reps/null.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { ObjectBox } = createFactories(require("./object-box")); diff --git a/devtools/client/jsonview/components/reps/number.js b/devtools/client/jsonview/components/reps/number.js index 5990d85460e..e30b7d01324 100644 --- a/devtools/client/jsonview/components/reps/number.js +++ b/devtools/client/jsonview/components/reps/number.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { ObjectBox } = createFactories(require("./object-box")); diff --git a/devtools/client/jsonview/components/reps/object-box.js b/devtools/client/jsonview/components/reps/object-box.js index b5729c77f74..96624111e28 100644 --- a/devtools/client/jsonview/components/reps/object-box.js +++ b/devtools/client/jsonview/components/reps/object-box.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; /** diff --git a/devtools/client/jsonview/components/reps/object-link.js b/devtools/client/jsonview/components/reps/object-link.js index 4f354855530..eb5258d6abe 100644 --- a/devtools/client/jsonview/components/reps/object-link.js +++ b/devtools/client/jsonview/components/reps/object-link.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; /** diff --git a/devtools/client/jsonview/components/reps/object.js b/devtools/client/jsonview/components/reps/object.js index e046fc14e96..1a4fbcf4543 100644 --- a/devtools/client/jsonview/components/reps/object.js +++ b/devtools/client/jsonview/components/reps/object.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { ObjectBox } = createFactories(require("./object-box")); const { Caption } = createFactories(require("./caption")); diff --git a/devtools/client/jsonview/components/reps/rep-utils.js b/devtools/client/jsonview/components/reps/rep-utils.js index 95ecfef1ccc..fd6423ee470 100644 --- a/devtools/client/jsonview/components/reps/rep-utils.js +++ b/devtools/client/jsonview/components/reps/rep-utils.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); /** * Create React factories for given arguments. diff --git a/devtools/client/jsonview/components/reps/rep.js b/devtools/client/jsonview/components/reps/rep.js index c6d4f23259d..10c6cdb919f 100644 --- a/devtools/client/jsonview/components/reps/rep.js +++ b/devtools/client/jsonview/components/reps/rep.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); // Load all existing rep templates const { Undefined } = require("./undefined"); diff --git a/devtools/client/jsonview/components/reps/string.js b/devtools/client/jsonview/components/reps/string.js index 1c31a75892d..4e477224674 100644 --- a/devtools/client/jsonview/components/reps/string.js +++ b/devtools/client/jsonview/components/reps/string.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { ObjectBox } = createFactories(require("./object-box")); diff --git a/devtools/client/jsonview/components/reps/tabs.js b/devtools/client/jsonview/components/reps/tabs.js index 8bf71826895..0db619e0d2c 100644 --- a/devtools/client/jsonview/components/reps/tabs.js +++ b/devtools/client/jsonview/components/reps/tabs.js @@ -8,7 +8,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; /** diff --git a/devtools/client/jsonview/components/reps/toolbar.js b/devtools/client/jsonview/components/reps/toolbar.js index 80696ebc1fe..a2f7a3640e5 100644 --- a/devtools/client/jsonview/components/reps/toolbar.js +++ b/devtools/client/jsonview/components/reps/toolbar.js @@ -8,7 +8,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; /** diff --git a/devtools/client/jsonview/components/reps/tree-view.js b/devtools/client/jsonview/components/reps/tree-view.js index 3452e1c6258..490ccfb3cf2 100644 --- a/devtools/client/jsonview/components/reps/tree-view.js +++ b/devtools/client/jsonview/components/reps/tree-view.js @@ -7,7 +7,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { Rep } = createFactories(require("./rep")); const { StringRep } = require("./string"); diff --git a/devtools/client/jsonview/components/reps/undefined.js b/devtools/client/jsonview/components/reps/undefined.js index 5b48ac17509..eb77b552e6a 100644 --- a/devtools/client/jsonview/components/reps/undefined.js +++ b/devtools/client/jsonview/components/reps/undefined.js @@ -9,7 +9,7 @@ define(function(require, exports, module) { // Dependencies -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./rep-utils"); const { ObjectBox } = createFactories(require("./object-box")); diff --git a/devtools/client/jsonview/components/search-box.js b/devtools/client/jsonview/components/search-box.js index c5ab3c58c9f..d2d67f91b61 100644 --- a/devtools/client/jsonview/components/search-box.js +++ b/devtools/client/jsonview/components/search-box.js @@ -8,7 +8,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const DOM = React.DOM; diff --git a/devtools/client/jsonview/components/text-panel.js b/devtools/client/jsonview/components/text-panel.js index a194ee7e906..9060ce149bb 100644 --- a/devtools/client/jsonview/components/text-panel.js +++ b/devtools/client/jsonview/components/text-panel.js @@ -6,7 +6,7 @@ define(function(require, exports, module) { -const React = require("react"); +const React = require("devtools/client/shared/vendor/react"); const { createFactories } = require("./reps/rep-utils"); const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar")); const DOM = React.DOM; diff --git a/devtools/client/jsonview/json-viewer.js b/devtools/client/jsonview/json-viewer.js index 5300db3ae37..45951b6cb65 100644 --- a/devtools/client/jsonview/json-viewer.js +++ b/devtools/client/jsonview/json-viewer.js @@ -7,9 +7,7 @@ define(function(require, exports, module) { // ReactJS -const ReactDOM = require("react-dom"); - -// RDP Inspector +const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { createFactories } = require("./components/reps/rep-utils"); const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area")); diff --git a/devtools/client/jsonview/viewer-config.js b/devtools/client/jsonview/viewer-config.js index 9b881ebddb6..f3843aef0e4 100644 --- a/devtools/client/jsonview/viewer-config.js +++ b/devtools/client/jsonview/viewer-config.js @@ -16,15 +16,19 @@ * * The path mapping uses paths fallback (a feature supported by RequireJS) * See also: http://requirejs.org/docs/api.html#pathsfallbacks + * + * React module ID is using exactly the same (relative) path as the rest + * of the code base, so it's consistent and modules can be easily reused. */ require.config({ baseUrl: ".", paths: { - "react": [ + "devtools/client/shared/vendor/react": [ "resource://devtools/client/shared/vendor/react-dev", "resource://devtools/client/shared/vendor/react" ], - "react-dom": "resource://devtools/client/shared/vendor/react-dom" + "devtools/client/shared/vendor/react-dom": + "resource://devtools/client/shared/vendor/react-dom" } }); diff --git a/devtools/client/shared/browser-loader.js b/devtools/client/shared/browser-loader.js index d2d5f6f31fa..4451c442bed 100644 --- a/devtools/client/shared/browser-loader.js +++ b/devtools/client/shared/browser-loader.js @@ -1,9 +1,15 @@ +/* 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"; + var { classes: Cc, interfaces: Ci, utils: Cu } = Components; const loaders = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); const { devtools, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); const { joinURI } = devtools.require("devtools/shared/path"); const BROWSER_BASED_DIRS = [ + "resource://devtools/client/jsonview", "resource://devtools/client/shared/vendor", "resource://devtools/client/shared/components", "resource://devtools/client/shared/redux" @@ -54,6 +60,13 @@ function BrowserLoader(baseURI, window) { } return require(uri); + }, + globals: { + // Make sure 'define' function exists. This allows reusing AMD modules. + define: function(callback) { + callback(this.require, this.exports, this.module); + return this.exports; + } } }; diff --git a/devtools/client/shared/vendor/react-dom.js b/devtools/client/shared/vendor/react-dom.js index cd1acd8e53d..b7dafb1090a 100644 --- a/devtools/client/shared/vendor/react-dom.js +++ b/devtools/client/shared/vendor/react-dom.js @@ -6,7 +6,7 @@ // RequireJS } else if (typeof define === "function" && define.amd) { - define(['react'], f); + define(['devtools/client/shared/vendor/react'], f); // + From 75e848d2dddfbce5e67cf373b7e17092aa8b3741 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 13 Jan 2016 15:29:54 +0100 Subject: [PATCH 38/60] Bug 1207130 - Add a checkbox for automatic error reporting to about:certerror r=Gijs,past --- browser/base/content/aboutNetError.xhtml | 6 +- .../aboutcerterror/aboutCertError.xhtml | 51 +- browser/base/content/browser.js | 4 +- browser/base/content/content.js | 170 ++++++- browser/base/content/test/general/browser.ini | 3 +- .../test/general/browser_ssl_error_reports.js | 442 +++++++----------- .../browser_ssl_error_reports_content.js | 25 - ...ning_reports.sjs => ssl_error_reports.sjs} | 14 + .../en-US/chrome/browser/aboutCertError.dtd | 5 + .../en-US/chrome/overrides/netError.dtd | 1 - browser/themes/shared/aboutCertError.css | 8 + browser/themes/shared/aboutNetError.css | 1 - testing/profiles/prefs_general.js | 2 +- 13 files changed, 402 insertions(+), 330 deletions(-) delete mode 100644 browser/base/content/test/general/browser_ssl_error_reports_content.js rename browser/base/content/test/general/{pinning_reports.sjs => ssl_error_reports.sjs} (90%) diff --git a/browser/base/content/aboutNetError.xhtml b/browser/base/content/aboutNetError.xhtml index f7b9e507a3f..fcb2fe19da9 100644 --- a/browser/base/content/aboutNetError.xhtml +++ b/browser/base/content/aboutNetError.xhtml @@ -260,15 +260,12 @@ var event = new CustomEvent("AboutNetErrorSetAutomatic", {bubbles:true, detail:evt.target.checked}); document.dispatchEvent(event); - if (evt.target.checked && reportBtn.style.display != "none") { + if (evt.target.checked) { sendErrorReport(); } }, false); - var reportBtn = document.getElementById('reportCertificateError'); var retryBtn = document.getElementById('reportCertificateErrorRetry'); - - reportBtn.addEventListener('click', sendErrorReport, false); retryBtn.addEventListener('click', sendErrorReport, false); } } @@ -532,7 +529,6 @@ - &errorReporting.sending; &errorReporting.sent; diff --git a/browser/base/content/aboutcerterror/aboutCertError.xhtml b/browser/base/content/aboutcerterror/aboutCertError.xhtml index 6df03e3e61e..306dfc1a666 100644 --- a/browser/base/content/aboutcerterror/aboutCertError.xhtml +++ b/browser/base/content/aboutcerterror/aboutCertError.xhtml @@ -88,6 +88,32 @@ toggleVisibility('advancedPanel'); } + var checkbox = document.getElementById("automaticallyReportInFuture"); + checkbox.addEventListener("change", function ({target: {checked}}) { + document.dispatchEvent(new CustomEvent("AboutCertErrorSetAutomatic", { + detail: checked, + bubbles: true + })); + }); + + var retryBtn = document.getElementById("reportCertificateErrorRetry"); + retryBtn.addEventListener("click", function () { + document.dispatchEvent(new CustomEvent("AboutCertErrorSendReport", { + bubbles: true + })); + }); + + addEventListener("AboutCertErrorOptions", function (event) { + var options = JSON.parse(event.detail); + if (options && options.enabled) { + // Display error reporting UI + document.getElementById("certificateErrorReporting").style.display = "block"; + + // set the checkbox + checkbox.checked = !!options.automatic; + } + }, true, true); + // Disallow overrides if this is a Strict-Transport-Security // host and the cert is bad (STS Spec section 7.3) or if the // certerror is in a frame (bug 633691). @@ -254,11 +280,26 @@
- - + + + +
+

+ + + + + + &errorReporting.sending; + &errorReporting.sent; + +

+
+ + + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index b6946926bef..d2d6186aa47 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2673,7 +2673,7 @@ var BrowserOnClick = { } break; case "Browser:SendSSLErrorReport": - this.onSSLErrorReport(msg.target, msg.data.elementId, + this.onSSLErrorReport(msg.target, msg.data.documentURI, msg.data.location, msg.data.securityInfo); @@ -2704,7 +2704,7 @@ var BrowserOnClick = { } }, - onSSLErrorReport: function(browser, elementId, documentURI, location, securityInfo) { + onSSLErrorReport: function(browser, documentURI, location, securityInfo) { function showReportStatus(reportStatus) { gBrowser.selectedBrowser .messageManager diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 766ea9ced10..ba174eb6a70 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -207,6 +207,143 @@ const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1; const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6; const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7; +var AboutCertErrorListener = { + init(chromeGlobal) { + addMessageListener("AboutCertErrorDetails", this); + addMessageListener("Browser:SSLErrorReportStatus", this); + chromeGlobal.addEventListener("AboutCertErrorLoad", this, false, true); + chromeGlobal.addEventListener("AboutCertErrorSetAutomatic", this, false, true); + chromeGlobal.addEventListener("AboutCertErrorSendReport", this, false, true); + }, + + get isAboutCertError() { + return content.document.documentURI.startsWith("about:certerror"); + }, + + handleEvent(event) { + if (!this.isAboutCertError) { + return; + } + + switch (event.type) { + case "AboutCertErrorLoad": + this.onLoad(event); + break; + case "AboutCertErrorSetAutomatic": + this.onSetAutomatic(event); + break; + case "AboutCertErrorSendReport": + this.onSendReport(); + break; + } + }, + + receiveMessage(msg) { + if (!this.isAboutCertError) { + return; + } + + switch (msg.name) { + case "AboutCertErrorDetails": + this.onDetails(msg); + break; + case "Browser:SSLErrorReportStatus": + this.onReportStatus(msg); + break; + } + }, + + onLoad(event) { + let originalTarget = event.originalTarget; + let ownerDoc = originalTarget.ownerDocument; + ClickEventHandler.onAboutCertError(originalTarget, ownerDoc); + + let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic"); + content.dispatchEvent(new content.CustomEvent("AboutCertErrorOptions", { + detail: JSON.stringify({ + enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"), + automatic, + }) + })); + + if (automatic) { + this.onSendReport(); + } + }, + + onDetails(msg) { + let div = content.document.getElementById("certificateErrorText"); + div.textContent = msg.data.info; + }, + + onSetAutomatic(event) { + if (event.detail) { + this.onSendReport(); + } + + sendAsyncMessage("Browser:SetSSLErrorReportAuto", { + automatic: event.detail + }); + }, + + onSendReport() { + let doc = content.document; + let location = doc.location.href; + + let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + + let serializable = docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + + let serializedSecurityInfo = serhelper.serializeToString(serializable); + + sendAsyncMessage("Browser:SendSSLErrorReport", { + documentURI: doc.documentURI, + location: {hostname: doc.location.hostname, port: doc.location.port}, + securityInfo: serializedSecurityInfo + }); + }, + + onReportStatus(msg) { + let doc = content.document; + if (doc.documentURI != msg.data.documentURI) { + return; + } + + let reportSendingMsg = doc.getElementById("reportSendingMessage"); + let reportSentMsg = doc.getElementById("reportSentMessage"); + let retryBtn = doc.getElementById("reportCertificateErrorRetry"); + + switch (msg.data.reportStatus) { + case "activity": + // Hide the button that was just clicked + retryBtn.style.removeProperty("display"); + reportSentMsg.style.removeProperty("display"); + reportSendingMsg.style.display = "block"; + break; + case "error": + // show the retry button + retryBtn.style.display = "block"; + reportSendingMsg.style.removeProperty("display"); + sendAsyncMessage("Browser:SSLErrorReportTelemetry", + {reportStatus: TLS_ERROR_REPORT_TELEMETRY_FAILURE}); + break; + case "complete": + // Show a success indicator + reportSentMsg.style.display = "block"; + reportSendingMsg.style.removeProperty("display"); + sendAsyncMessage("Browser:SSLErrorReportTelemetry", + {reportStatus: TLS_ERROR_REPORT_TELEMETRY_SUCCESS}); + break; + } + } +}; + +AboutCertErrorListener.init(this); + + var AboutNetErrorListener = { init: function(chromeGlobal) { chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true); @@ -283,7 +420,6 @@ var AboutNetErrorListener = { let reportSendingMsg = contentDoc.getElementById("reportSendingMessage"); let reportSentMsg = contentDoc.getElementById("reportSentMessage"); - let reportBtn = contentDoc.getElementById("reportCertificateError"); let retryBtn = contentDoc.getElementById("reportCertificateErrorRetry"); addMessageListener("Browser:SSLErrorReportStatus", function(message) { @@ -293,7 +429,6 @@ var AboutNetErrorListener = { switch(message.data.reportStatus) { case "activity": // Hide the button that was just clicked - reportBtn.style.display = "none"; retryBtn.style.display = "none"; reportSentMsg.style.display = "none"; reportSendingMsg.style.removeProperty("display"); @@ -319,20 +454,22 @@ var AboutNetErrorListener = { let location = contentDoc.location.href; let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] - .getService(Ci.nsISerializationHelper); + .getService(Ci.nsISerializationHelper); - let serializable = docShell.failedChannel.securityInfo - .QueryInterface(Ci.nsITransportSecurityInfo) - .QueryInterface(Ci.nsISerializable); + let serializable = docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); let serializedSecurityInfo = serhelper.serializeToString(serializable); sendAsyncMessage("Browser:SendSSLErrorReport", { - elementId: evt.target.id, - documentURI: contentDoc.documentURI, - location: {hostname: contentDoc.location.hostname, port: contentDoc.location.port}, - securityInfo: serializedSecurityInfo - }); + documentURI: contentDoc.documentURI, + location: { + hostname: contentDoc.location.hostname, + port: contentDoc.location.port + }, + securityInfo: serializedSecurityInfo + }); }, onOverride: function(evt) { @@ -553,17 +690,6 @@ addEventListener("DOMServiceWorkerFocusClient", function(event) { sendAsyncMessage("DOMServiceWorkerFocusClient", {}); }, false); -addEventListener("AboutCertErrorLoad", function(event) { - let originalTarget = event.originalTarget; - let ownerDoc = originalTarget.ownerDocument; - ClickEventHandler.onAboutCertError(originalTarget, ownerDoc); -}, false, true); - -addMessageListener("AboutCertErrorDetails", function(message) { - let div = content.document.getElementById("certificateErrorText"); - div.textContent = message.data.info; -}); - ContentWebRTC.init(); addMessageListener("rtcpeer:Allow", ContentWebRTC); addMessageListener("rtcpeer:Deny", ContentWebRTC); diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index 3f0e9665ed4..5704cc17e64 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -16,7 +16,6 @@ support-files = browser_fxa_oauth_with_keys.html browser_fxa_web_channel.html browser_registerProtocolHandler_notification.html - browser_ssl_error_reports_content.js browser_star_hsts.sjs browser_tab_dragdrop2_frame1.xul browser_web_channel.html @@ -79,7 +78,7 @@ support-files = page_style_sample.html parsingTestHelpers.jsm pinning_headers.sjs - pinning_reports.sjs + ssl_error_reports.sjs popup_blocker.html print_postdata.sjs redirect_bug623155.sjs diff --git a/browser/base/content/test/general/browser_ssl_error_reports.js b/browser/base/content/test/general/browser_ssl_error_reports.js index c66bfc71859..266311088b2 100644 --- a/browser/base/content/test/general/browser_ssl_error_reports.js +++ b/browser/base/content/test/general/browser_ssl_error_reports.js @@ -1,307 +1,217 @@ "use strict"; -var badChainURL = "https://badchain.include-subdomains.pinning.example.com"; -var noCertURL = "https://fail-handshake.example.com"; -var enabledPref = false; -var automaticPref = false; -var urlPref = "security.ssl.errorReporting.url"; -var enforcement_level = 1; -var ROOT = getRootDirectory(gTestPath); +const URL_REPORTS = "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?"; +const URL_BAD_CHAIN = "https://badchain.include-subdomains.pinning.example.com/"; +const URL_NO_CERT = "https://fail-handshake.example.com/"; +const URL_BAD_CERT = "https://expired.example.com/"; +const URL_BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443/"; +const ROOT = getRootDirectory(gTestPath); +const PREF_REPORT_ENABLED = "security.ssl.errorReporting.enabled"; +const PREF_REPORT_AUTOMATIC = "security.ssl.errorReporting.automatic"; +const PREF_REPORT_URL = "security.ssl.errorReporting.url"; SimpleTest.requestCompleteLog(); -add_task(function* test_send_report_manual_badchain() { - yield testSendReportManual(badChainURL, "succeed"); -}); +Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2); -add_task(function* test_send_report_manual_nocert() { - yield testSendReportManual(noCertURL, "nocert"); -}); - -// creates a promise of the message in an error page -function createNetworkErrorMessagePromise(aBrowser) { - let promise = new Promise(function(resolve, reject) { - let originalDocumentURI = aBrowser.contentDocument.documentURI; - - let loadedListener = function() { - let doc = aBrowser.contentDocument; - - if (doc && doc.getElementById("reportCertificateError")) { - let documentURI = doc.documentURI; - - aBrowser.removeEventListener("DOMContentLoaded", loadedListener, true); - let matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI); - if (!matchArray) { - reject("no network error message found in URI"); - return; - } - - let errorMsg = matchArray[1]; - resolve(decodeURIComponent(errorMsg)); - } - }; - aBrowser.addEventListener("DOMContentLoaded", loadedListener, true); - }); - - return promise; +function cleanup() { + Services.prefs.clearUserPref(PREF_REPORT_ENABLED); + Services.prefs.clearUserPref(PREF_REPORT_AUTOMATIC); + Services.prefs.clearUserPref(PREF_REPORT_URL); } -// check we can set the 'automatically send' pref -add_task(function* test_set_automatic() { - setup(); - let tab = gBrowser.addTab(badChainURL, {skipAnimation: true}); - let browser = tab.linkedBrowser; - let mm = browser.messageManager; - mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true); - - gBrowser.selectedTab = tab; - - // ensure we have the correct error message from about:neterror - let netError = createNetworkErrorMessagePromise(browser); - yield netError; - - // ensure that setting automatic when unset works - let prefEnabled = new Promise(function(resolve, reject){ - let prefUpdateListener = function() { - mm.removeMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener); - if (Services.prefs.getBoolPref("security.ssl.errorReporting.automatic")) { - resolve(); - } else { - reject(); - } - }; - mm.addMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener); - }); - - mm.sendAsyncMessage("ssler-test:SetAutoPref",{value:true}); - - yield prefEnabled; - - // ensure un-setting automatic, when set, works - let prefDisabled = new Promise(function(resolve, reject){ - let prefUpdateListener = function () { - mm.removeMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener); - if (!Services.prefs.getBoolPref("security.ssl.errorReporting.automatic")) { - resolve(); - } else { - reject(); - } - }; - mm.addMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener); - }); - - mm.sendAsyncMessage("ssler-test:SetAutoPref",{value:false}); - - yield prefDisabled; - - gBrowser.removeTab(tab); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.cert_pinning.enforcement_level"); cleanup(); }); -// test that manual report sending (with button clicks) works -var testSendReportManual = function*(testURL, suffix) { - setup(); - Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true); - Services.prefs.setCharPref("security.ssl.errorReporting.url", - "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?" + suffix); +add_task(function* test_send_report_neterror() { + yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror"); + yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror"); + yield testSendReportFailRetry(URL_NO_CERT, "nocert", "neterror"); + yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror"); +}); - let tab = gBrowser.addTab(testURL, {skipAnimation: true}); +add_task(function* test_send_report_certerror() { + yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror"); + yield testSendReportFailRetry(URL_BAD_CERT, "badcert", "certerror"); + yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror"); +}); + +add_task(function* test_send_disabled() { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true); + Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid"); + + // Check with enabled=false but automatic=true. + yield testSendReportDisabled(URL_NO_CERT, "neterror"); + yield testSendReportDisabled(URL_BAD_CERT, "certerror"); + + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false); + + // Check again with both prefs false. + yield testSendReportDisabled(URL_NO_CERT, "neterror"); + yield testSendReportDisabled(URL_BAD_CERT, "certerror"); + cleanup(); +}); + +function* testSendReportAutomatically(testURL, suffix, errorURISuffix) { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true); + Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix); + + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); let browser = tab.linkedBrowser; - let mm = browser.messageManager; - mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true); - gBrowser.selectedTab = tab; + // Load the page and wait for the error report submission. + let promiseReport = createErrorReportPromise(browser); + browser.loadURI(testURL); + yield promiseReport; + ok(true, "SSL error report submitted successfully"); - // ensure we have the correct error message from about:neterror - let netError = createNetworkErrorMessagePromise(browser); - yield netError; - netError.then(function(val){ - is(val.startsWith("An error occurred during a connection to"), true, - "ensure the correct error message came from about:neterror"); - }); - - let btn = browser.contentDocument.getElementById("reportCertificateError"); - let deferredReportSucceeds = Promise.defer(); - - // ensure we see the correct statuses in the correct order... - let statusListener = function() { - let active = false; - return function(message) { - switch(message.data.reportStatus) { - case "activity": - if (!active) { - active = true; - } - break; - case "complete": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - if (active) { - deferredReportSucceeds.resolve(message.data.reportStatus); - } else { - deferredReportSucceeds.reject('activity should be seen before success'); - } - break; - case "error": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - deferredReportSucceeds.reject(); - break; - } - }; - }(); - mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - - // ... once the button is clicked, that is - mm.sendAsyncMessage("ssler-test:SendBtnClick",{}); - - yield deferredReportSucceeds.promise; + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); + // Cleanup. gBrowser.removeTab(tab); cleanup(); }; -// test that automatic sending works -add_task(function* test_send_report_auto() { - setup(); - Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true); - Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true); - Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?succeed"); +function* testSendReportFailRetry(testURL, suffix, errorURISuffix) { + try { + yield testSendReportAutomatically(testURL, "error", errorURISuffix); + ok(false, "sending a report should have failed"); + } catch (err) { + ok(err, "saw a failure notification"); + } - let tab = gBrowser.addTab(badChainURL, {skipAnimation: true}); - let browser = tab.linkedBrowser; - let mm = browser.messageManager; - mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true); + Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix); - gBrowser.selectedTab = tab; - - - // Ensure the error page loads - let netError = createNetworkErrorMessagePromise(browser); - yield netError; - - let reportWillStart = Promise.defer(); - let startListener = function() { - mm.removeMessageListener("Browser:SendSSLErrorReport", startListener); - reportWillStart.resolve(); - }; - mm.addMessageListener("Browser:SendSSLErrorReport", startListener); - - let deferredReportSucceeds = Promise.defer(); - - let statusListener = function(message) { - switch(message.data.reportStatus) { - case "complete": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - deferredReportSucceeds.resolve(message.data.reportStatus); - break; - case "error": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - deferredReportSucceeds.reject(); - break; - } - }; - - mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - - // Ensure the report is sent with no interaction - yield deferredReportSucceeds.promise; - - gBrowser.removeTab(tab); - cleanup(); -}); - -// test that an error is shown if there's a problem with the report server -add_task(function* test_send_report_error() { - setup(); - // set up prefs so error send is automatic and an error will occur - Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true); - Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true); - Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?error"); - - // load the test URL so error page is seen - let tab = gBrowser.addTab(badChainURL, {skipAnimation: true}); - let browser = tab.linkedBrowser; - gBrowser.selectedTab = tab; - let mm = browser.messageManager; - mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true); - - let reportErrors = new Promise(function(resolve, reject) { - let statusListener = function(message) { - switch(message.data.reportStatus) { - case "complete": - reject(message.data.reportStatus); - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - break; - case "error": - resolve(message.data.reportStatus); - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - break; - } - }; - mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener); + let browser = gBrowser.selectedBrowser; + let promiseReport = createErrorReportPromise(browser); + let promiseRetry = ContentTask.spawn(browser, null, function* () { + content.document.getElementById("reportCertificateErrorRetry").click(); }); - // check that errors are sent - yield reportErrors; + yield Promise.all([promiseReport, promiseRetry]); + ok(true, "SSL error report submitted successfully"); - gBrowser.removeTab(tab); + // Cleanup. + gBrowser.removeCurrentTab(); cleanup(); -}); +} -add_task(function* test_send_report_disabled() { - setup(); - Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", false); - Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://offdomain.com"); +function* testSetAutomatic(testURL, suffix, errorURISuffix) { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false); + Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix); - let tab = gBrowser.addTab(badChainURL, {skipAnimation: true}); + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); let browser = tab.linkedBrowser; - let mm = browser.messageManager; - mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true); - gBrowser.selectedTab = tab; + // Load the page. + browser.loadURI(testURL); + yield promiseErrorPageLoaded(browser); - // Ensure we have an error page - let netError = createNetworkErrorMessagePromise(browser); - yield netError; + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); - let reportErrors = new Promise(function(resolve, reject) { - let statusListener = function(message) { - switch(message.data.reportStatus) { - case "complete": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - reject(message.data.reportStatus); - break; - case "error": - mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener); - resolve(message.data.reportStatus); - break; - } - }; - mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener); + // Click the checkbox, enable automatic error reports. + let promiseReport = createErrorReportPromise(browser); + yield ContentTask.spawn(browser, null, function* () { + content.document.getElementById("automaticallyReportInFuture").click(); }); - // click the button - mm.sendAsyncMessage("ssler-test:SendBtnClick",{forceUI:true}); + // Wait for the error report submission. + yield promiseReport; + ok(true, "SSL error report submitted successfully"); - // check we get an error - yield reportErrors; + let isAutomaticReportingEnabled = () => + Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC); + // Check that the pref was flipped. + ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled"); + + // Disable automatic error reports. + yield ContentTask.spawn(browser, null, function* () { + content.document.getElementById("automaticallyReportInFuture").click(); + }); + + // Check that the pref was flipped. + ok(!isAutomaticReportingEnabled(), "automatic SSL report submission disabled"); + + // Cleanup. gBrowser.removeTab(tab); cleanup(); -}); - -function setup() { - // ensure the relevant prefs are set - enabledPref = Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"); - automaticPref = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic"); - urlPref = Services.prefs.getCharPref("security.ssl.errorReporting.url"); - - enforcement_level = Services.prefs.getIntPref("security.cert_pinning.enforcement_level"); - Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2); } -function cleanup() { - // reset prefs for other tests in the run - Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", enabledPref); - Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", automaticPref); - Services.prefs.setCharPref("security.ssl.errorReporting.url", urlPref); +function* testSendReportDisabled(testURL, errorURISuffix) { + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + + // Load the page. + browser.loadURI(testURL); + yield promiseErrorPageLoaded(browser); + + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); + + // Check that the error reporting section is hidden. + let hidden = yield ContentTask.spawn(browser, null, function* () { + let section = content.document.getElementById("certificateErrorReporting"); + return content.getComputedStyle(section).display == "none"; + }); + ok(hidden, "error reporting section should be hidden"); + + // Cleanup. + gBrowser.removeTab(tab); +} + +function createErrorReportPromise(browser) { + return ContentTask.spawn(browser, null, function* () { + let type = "Browser:SSLErrorReportStatus"; + let active = false; + + yield new Promise((resolve, reject) => { + addMessageListener(type, function onReportStatus(message) { + switch (message.data.reportStatus) { + case "activity": + active = true; + break; + case "complete": + removeMessageListener(type, onReportStatus); + if (active) { + resolve(message.data.reportStatus); + } else { + reject("activity should be seen before success"); + } + break; + case "error": + removeMessageListener(type, onReportStatus); + reject("sending the report failed"); + break; + } + }); + }); + }); +} + +function promiseErrorPageLoaded(browser) { + return new Promise(resolve => { + browser.addEventListener("DOMContentLoaded", function onLoad() { + browser.removeEventListener("DOMContentLoaded", onLoad, false, true); + resolve(); + }, false, true); + }); +} + +function checkErrorPage(browser, suffix) { + return ContentTask.spawn(browser, null, function* () { + return content.document.documentURI; + }).then(uri => { + ok(uri.startsWith(`about:${suffix}`), "correct error page loaded"); + }); } diff --git a/browser/base/content/test/general/browser_ssl_error_reports_content.js b/browser/base/content/test/general/browser_ssl_error_reports_content.js deleted file mode 100644 index 772ed812040..00000000000 --- a/browser/base/content/test/general/browser_ssl_error_reports_content.js +++ /dev/null @@ -1,25 +0,0 @@ -addMessageListener("Browser:SSLErrorReportStatus", function(message) { - sendSyncMessage("ssler-test:SSLErrorReportStatus", {reportStatus:message.data.reportStatus}); -}); - -addMessageListener("ssler-test:SetAutoPref", function(message) { - let checkbox = content.document.getElementById("automaticallyReportInFuture"); - - // we use "click" because otherwise the 'changed' event will not fire - if (checkbox.checked != message.data.value) { - checkbox.click(); - } - - sendSyncMessage("ssler-test:AutoPrefUpdated", {}); -}); - -addMessageListener("ssler-test:SendBtnClick", function(message) { - if (message.data && message.data.forceUI) { - content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions", - { - detail: "{\"enabled\": true, \"automatic\": false}" - })); - } - let btn = content.document.getElementById("reportCertificateError"); - btn.click(); -}); diff --git a/browser/base/content/test/general/pinning_reports.sjs b/browser/base/content/test/general/ssl_error_reports.sjs similarity index 90% rename from browser/base/content/test/general/pinning_reports.sjs rename to browser/base/content/test/general/ssl_error_reports.sjs index fde7d16ae4d..454d13b7f99 100644 --- a/browser/base/content/test/general/pinning_reports.sjs +++ b/browser/base/content/test/general/ssl_error_reports.sjs @@ -61,6 +61,20 @@ function handleRequest(request, response) { return; } + // if all is as expected, send the 201 the client expects + response.setStatusLine("1.1", 201, "Created"); + response.write("OK"); + break; + case "badcert": + report = parseReport(request); + certChain = report.failedCertChain; + + if (!certChain || certChain.length != 2) { + response.setStatusLine("1.1", 500, "Server error"); + response.write("The report contained an unexpected chain"); + return; + } + // if all is as expected, send the 201 the client expects response.setStatusLine("1.1", 201, "Created"); response.write("OK"); diff --git a/browser/locales/en-US/chrome/browser/aboutCertError.dtd b/browser/locales/en-US/chrome/browser/aboutCertError.dtd index 4858d5722f2..f021844a750 100644 --- a/browser/locales/en-US/chrome/browser/aboutCertError.dtd +++ b/browser/locales/en-US/chrome/browser/aboutCertError.dtd @@ -34,3 +34,8 @@ tampering with your connection."> you know there's a good reason why this site doesn't use trusted identification."> + + + + + diff --git a/browser/locales/en-US/chrome/overrides/netError.dtd b/browser/locales/en-US/chrome/overrides/netError.dtd index 3c5a2ddcd12..9a3d1d778cf 100644 --- a/browser/locales/en-US/chrome/overrides/netError.dtd +++ b/browser/locales/en-US/chrome/overrides/netError.dtd @@ -208,7 +208,6 @@ functionality specific to firefox. --> - diff --git a/browser/themes/shared/aboutCertError.css b/browser/themes/shared/aboutCertError.css index 636f878ca74..7d68a79d327 100644 --- a/browser/themes/shared/aboutCertError.css +++ b/browser/themes/shared/aboutCertError.css @@ -96,6 +96,7 @@ body { /* Advanced section is hidden via inline styles until the link is clicked */ #advancedPanel { + position: absolute; background-color: white; color: var(--in-content-text-color); border: 1px lightgray solid; @@ -110,3 +111,10 @@ body { .hostname { font-weight: bold; } + +#reportCertificateErrorRetry, +#certificateErrorReporting, +#reportSendingMessage, +#reportSentMessage { + display: none; +} diff --git a/browser/themes/shared/aboutNetError.css b/browser/themes/shared/aboutNetError.css index cc5513ceaae..36ea3e0aa6c 100644 --- a/browser/themes/shared/aboutNetError.css +++ b/browser/themes/shared/aboutNetError.css @@ -116,7 +116,6 @@ button:disabled { } #certificateErrorReporting, -#reportCertificateError, #reportSentMessage { display: none; } diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index 91fb2bc1e25..72a10cce8a8 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -265,7 +265,7 @@ user_pref('identity.fxaccounts.skipDeviceRegistration', true); user_pref("apz.content_response_timeout", 60000); // Make sure SSL Error reports don't hit the network -user_pref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?succeed"); +user_pref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?succeed"); // Make sure Translation won't hit the network. user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs"); From adc59ba2b022300f0792165f729d64b75a2fcb62 Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 19 Jan 2016 16:55:06 -0500 Subject: [PATCH 39/60] Bug 1224765 - add production version of React for devtools and use it in release modes r=fitzgen --- addon-sdk/source/lib/toolkit/loader.js | 18 +- addon-sdk/source/test/test-loader.js | 13 + devtools/client/shared/browser-loader.js | 10 +- devtools/client/shared/vendor/REACT_UPGRADING | 25 +- devtools/client/shared/vendor/moz.build | 11 +- devtools/client/shared/vendor/react-dev.js | 20762 ++++++++++++++++ devtools/client/shared/vendor/react-dom.js | 11 + devtools/client/shared/vendor/react.js | 1691 +- 8 files changed, 21725 insertions(+), 816 deletions(-) create mode 100644 devtools/client/shared/vendor/react-dev.js diff --git a/addon-sdk/source/lib/toolkit/loader.js b/addon-sdk/source/lib/toolkit/loader.js index cfef008cb37..98305d5326f 100644 --- a/addon-sdk/source/lib/toolkit/loader.js +++ b/addon-sdk/source/lib/toolkit/loader.js @@ -557,8 +557,24 @@ const resolveURI = iced(function resolveURI(id, mapping) { while (index < count) { let [ path, uri ] = mapping[index++]; - if (id.indexOf(path) === 0) + + // Strip off any trailing slashes to make comparisons simpler + let stripped = path.endsWith('/') ? path.slice(0, -1) : path; + + // We only want to match path segments explicitly. Examples: + // * "foo/bar" matches for "foo/bar" + // * "foo/bar" matches for "foo/bar/baz" + // * "foo/bar" does not match for "foo/bar-1" + // * "foo/bar/" does not match for "foo/bar" + // * "foo/bar/" matches for "foo/bar/baz" + // + // Check for an empty path, an exact match, or a substring match + // with the next character being a forward slash. + if(stripped === "" || + (id.indexOf(stripped) === 0 && + (id.length === path.length || id[stripped.length] === '/'))) { return normalizeExt(id.replace(path, uri)); + } } return void 0; // otherwise we raise a warning, see bug 910304 }); diff --git a/addon-sdk/source/test/test-loader.js b/addon-sdk/source/test/test-loader.js index 817da0e0c12..28e86ac32ac 100644 --- a/addon-sdk/source/test/test-loader.js +++ b/addon-sdk/source/test/test-loader.js @@ -387,6 +387,19 @@ exports["test require#resolve"] = function(assert) { assert.equal(foundRoot + "sdk/tabs.js", require.resolve("sdk/tabs"), "correct resolution of sdk module"); assert.equal(foundRoot + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module"); + + const localLoader = Loader({ + paths: { "foo/bar": "bizzle", + "foo/bar2/": "bizzle2", + // Just to make sure this doesn't match the first entry, + // let use resolve this module + "foo/bar-bar": "foo/bar-bar" } + }); + const localRequire = Require(localLoader, module); + assert.equal(localRequire.resolve("foo/bar"), "bizzle.js"); + assert.equal(localRequire.resolve("foo/bar/baz"), "bizzle/baz.js"); + assert.equal(localRequire.resolve("foo/bar-bar"), "foo/bar-bar.js"); + assert.equal(localRequire.resolve("foo/bar2/"), "bizzle2.js"); }; const modulesURI = require.resolve("toolkit/loader").replace("toolkit/loader.js", ""); diff --git a/devtools/client/shared/browser-loader.js b/devtools/client/shared/browser-loader.js index 4451c442bed..b26911fe296 100644 --- a/devtools/client/shared/browser-loader.js +++ b/devtools/client/shared/browser-loader.js @@ -8,6 +8,8 @@ var { classes: Cc, interfaces: Ci, utils: Cu } = Components; const loaders = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); const { devtools, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); const { joinURI } = devtools.require("devtools/shared/path"); +Cu.import("resource://gre/modules/AppConstants.jsm"); + const BROWSER_BASED_DIRS = [ "resource://devtools/client/jsonview", "resource://devtools/client/shared/vendor", @@ -42,12 +44,18 @@ const BROWSER_BASED_DIRS = [ */ function BrowserLoader(baseURI, window) { const loaderOptions = devtools.require("@loader/options"); + const dynamicPaths = {}; + + if(AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES) { + dynamicPaths["devtools/client/shared/vendor/react"] = + "resource://devtools/client/shared/vendor/react-dev"; + }; const opts = { id: "browser-loader", sharedGlobal: true, sandboxPrototype: window, - paths: Object.assign({}, loaderOptions.paths), + paths: Object.assign({}, dynamicPaths, loaderOptions.paths), invisibleToDebugger: loaderOptions.invisibleToDebugger, require: (id, require) => { const uri = require.resolve(id); diff --git a/devtools/client/shared/vendor/REACT_UPGRADING b/devtools/client/shared/vendor/REACT_UPGRADING index b100d4e92a9..6cf2bb179ce 100644 --- a/devtools/client/shared/vendor/REACT_UPGRADING +++ b/devtools/client/shared/vendor/REACT_UPGRADING @@ -1,24 +1,23 @@ -React has a dev and prod version. The dev version includes additional -sanity checks and better errors, but at a slight perf cost. The prod -version available on the web is by default minified, but we don't want -a minified version, so we need to build it ourselves. +We have a version of React that has a few minor patches on top of it: +https://github.com/mozilla/react. These instructions are how to +upgrade to the latest version of React. -In bug 1217979, we are only using react in development environment for now until -we can think of a way to conditionally build these different versions, so -the `react.js` here is the dev environment one and generated with the following steps: +First, rebase our fork on top of the latest version and publish it to +our repo. + +Next, build React. You should already have our fork locally; make sure +you are building our fork: https://github.com/mozilla/react -* git clone https://github.com/facebook/react.git && cd react * npm install * grunt build -* cp build/react-with-addons.js /devtools/client/shared/vendor/react.js +* cp build/react-with-addons.js /devtools/client/shared/vendor/react-dev.js + +Note that the last command above adds a `react-dev.js` file. You also +need to generated a production version of React: -For production, which we do not currently have: * NODE_ENV=production grunt build * cp build/react-with-addons.js /devtools/client/shared/vendor/react.js -The second build produces a non-minified React file but with all the -sanity checks that incur a perf hit removed. - You also need to copy the ReactDOM package. It requires React, so right now we are just manually changing the path from `react` to `devtools/client/shared/vendor/react`. diff --git a/devtools/client/shared/vendor/moz.build b/devtools/client/shared/vendor/moz.build index c9287cffb02..7e2aec4aa42 100644 --- a/devtools/client/shared/vendor/moz.build +++ b/devtools/client/shared/vendor/moz.build @@ -4,10 +4,17 @@ # 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/. -DevToolsModules( +modules = [] + +if CONFIG['DEBUG_JS_MODULES'] or CONFIG['MOZ_DEBUG']: + modules += ['react-dev.js'] + +modules += [ 'react-dom.js', 'react-redux.js', 'react.js', 'redux.js', 'seamless-immutable.js' -) +] + +DevToolsModules(*modules) diff --git a/devtools/client/shared/vendor/react-dev.js b/devtools/client/shared/vendor/react-dev.js new file mode 100644 index 00000000000..fc92a8c3e9f --- /dev/null +++ b/devtools/client/shared/vendor/react-dev.js @@ -0,0 +1,20762 @@ + /** + * React (with addons) v0.14.6 + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.React = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 8 && documentMode <= 11); + +/** + * Opera <= 12 includes TextEvent in window, but does not fire + * text input events. Rely on keypress instead. + */ +function isPresto() { + var opera = window.opera; + return typeof opera === 'object' && typeof opera.version === 'function' && parseInt(opera.version(), 10) <= 12; +} + +var SPACEBAR_CODE = 32; +var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE); + +var topLevelTypes = EventConstants.topLevelTypes; + +// Events and their corresponding property names. +var eventTypes = { + beforeInput: { + phasedRegistrationNames: { + bubbled: keyOf({ onBeforeInput: null }), + captured: keyOf({ onBeforeInputCapture: null }) + }, + dependencies: [topLevelTypes.topCompositionEnd, topLevelTypes.topKeyPress, topLevelTypes.topTextInput, topLevelTypes.topPaste] + }, + compositionEnd: { + phasedRegistrationNames: { + bubbled: keyOf({ onCompositionEnd: null }), + captured: keyOf({ onCompositionEndCapture: null }) + }, + dependencies: [topLevelTypes.topBlur, topLevelTypes.topCompositionEnd, topLevelTypes.topKeyDown, topLevelTypes.topKeyPress, topLevelTypes.topKeyUp, topLevelTypes.topMouseDown] + }, + compositionStart: { + phasedRegistrationNames: { + bubbled: keyOf({ onCompositionStart: null }), + captured: keyOf({ onCompositionStartCapture: null }) + }, + dependencies: [topLevelTypes.topBlur, topLevelTypes.topCompositionStart, topLevelTypes.topKeyDown, topLevelTypes.topKeyPress, topLevelTypes.topKeyUp, topLevelTypes.topMouseDown] + }, + compositionUpdate: { + phasedRegistrationNames: { + bubbled: keyOf({ onCompositionUpdate: null }), + captured: keyOf({ onCompositionUpdateCapture: null }) + }, + dependencies: [topLevelTypes.topBlur, topLevelTypes.topCompositionUpdate, topLevelTypes.topKeyDown, topLevelTypes.topKeyPress, topLevelTypes.topKeyUp, topLevelTypes.topMouseDown] + } +}; + +// Track whether we've ever handled a keypress on the space key. +var hasSpaceKeypress = false; + +/** + * Return whether a native keypress event is assumed to be a command. + * This is required because Firefox fires `keypress` events for key commands + * (cut, copy, select-all, etc.) even though no character is inserted. + */ +function isKeypressCommand(nativeEvent) { + return (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) && + // ctrlKey && altKey is equivalent to AltGr, and is not a command. + !(nativeEvent.ctrlKey && nativeEvent.altKey); +} + +/** + * Translate native top level events into event types. + * + * @param {string} topLevelType + * @return {object} + */ +function getCompositionEventType(topLevelType) { + switch (topLevelType) { + case topLevelTypes.topCompositionStart: + return eventTypes.compositionStart; + case topLevelTypes.topCompositionEnd: + return eventTypes.compositionEnd; + case topLevelTypes.topCompositionUpdate: + return eventTypes.compositionUpdate; + } +} + +/** + * Does our fallback best-guess model think this event signifies that + * composition has begun? + * + * @param {string} topLevelType + * @param {object} nativeEvent + * @return {boolean} + */ +function isFallbackCompositionStart(topLevelType, nativeEvent) { + return topLevelType === topLevelTypes.topKeyDown && nativeEvent.keyCode === START_KEYCODE; +} + +/** + * Does our fallback mode think that this event is the end of composition? + * + * @param {string} topLevelType + * @param {object} nativeEvent + * @return {boolean} + */ +function isFallbackCompositionEnd(topLevelType, nativeEvent) { + switch (topLevelType) { + case topLevelTypes.topKeyUp: + // Command keys insert or clear IME input. + return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1; + case topLevelTypes.topKeyDown: + // Expect IME keyCode on each keydown. If we get any other + // code we must have exited earlier. + return nativeEvent.keyCode !== START_KEYCODE; + case topLevelTypes.topKeyPress: + case topLevelTypes.topMouseDown: + case topLevelTypes.topBlur: + // Events are not possible without cancelling IME. + return true; + default: + return false; + } +} + +/** + * Google Input Tools provides composition data via a CustomEvent, + * with the `data` property populated in the `detail` object. If this + * is available on the event object, use it. If not, this is a plain + * composition event and we have nothing special to extract. + * + * @param {object} nativeEvent + * @return {?string} + */ +function getDataFromCustomEvent(nativeEvent) { + var detail = nativeEvent.detail; + if (typeof detail === 'object' && 'data' in detail) { + return detail.data; + } + return null; +} + +// Track the current IME composition fallback object, if any. +var currentComposition = null; + +/** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {?object} A SyntheticCompositionEvent. + */ +function extractCompositionEvent(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) { + var eventType; + var fallbackData; + + if (canUseCompositionEvent) { + eventType = getCompositionEventType(topLevelType); + } else if (!currentComposition) { + if (isFallbackCompositionStart(topLevelType, nativeEvent)) { + eventType = eventTypes.compositionStart; + } + } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) { + eventType = eventTypes.compositionEnd; + } + + if (!eventType) { + return null; + } + + if (useFallbackCompositionData) { + // The current composition is stored statically and must not be + // overwritten while composition continues. + if (!currentComposition && eventType === eventTypes.compositionStart) { + currentComposition = FallbackCompositionState.getPooled(topLevelTarget); + } else if (eventType === eventTypes.compositionEnd) { + if (currentComposition) { + fallbackData = currentComposition.getData(); + } + } + } + + var event = SyntheticCompositionEvent.getPooled(eventType, topLevelTargetID, nativeEvent, nativeEventTarget); + + if (fallbackData) { + // Inject data generated from fallback path into the synthetic event. + // This matches the property of native CompositionEventInterface. + event.data = fallbackData; + } else { + var customData = getDataFromCustomEvent(nativeEvent); + if (customData !== null) { + event.data = customData; + } + } + + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; +} + +/** + * @param {string} topLevelType Record from `EventConstants`. + * @param {object} nativeEvent Native browser event. + * @return {?string} The string corresponding to this `beforeInput` event. + */ +function getNativeBeforeInputChars(topLevelType, nativeEvent) { + switch (topLevelType) { + case topLevelTypes.topCompositionEnd: + return getDataFromCustomEvent(nativeEvent); + case topLevelTypes.topKeyPress: + /** + * If native `textInput` events are available, our goal is to make + * use of them. However, there is a special case: the spacebar key. + * In Webkit, preventing default on a spacebar `textInput` event + * cancels character insertion, but it *also* causes the browser + * to fall back to its default spacebar behavior of scrolling the + * page. + * + * Tracking at: + * https://code.google.com/p/chromium/issues/detail?id=355103 + * + * To avoid this issue, use the keypress event as if no `textInput` + * event is available. + */ + var which = nativeEvent.which; + if (which !== SPACEBAR_CODE) { + return null; + } + + hasSpaceKeypress = true; + return SPACEBAR_CHAR; + + case topLevelTypes.topTextInput: + // Record the characters to be added to the DOM. + var chars = nativeEvent.data; + + // If it's a spacebar character, assume that we have already handled + // it at the keypress level and bail immediately. Android Chrome + // doesn't give us keycodes, so we need to blacklist it. + if (chars === SPACEBAR_CHAR && hasSpaceKeypress) { + return null; + } + + return chars; + + default: + // For other native event types, do nothing. + return null; + } +} + +/** + * For browsers that do not provide the `textInput` event, extract the + * appropriate string to use for SyntheticInputEvent. + * + * @param {string} topLevelType Record from `EventConstants`. + * @param {object} nativeEvent Native browser event. + * @return {?string} The fallback string for this `beforeInput` event. + */ +function getFallbackBeforeInputChars(topLevelType, nativeEvent) { + // If we are currently composing (IME) and using a fallback to do so, + // try to extract the composed characters from the fallback object. + if (currentComposition) { + if (topLevelType === topLevelTypes.topCompositionEnd || isFallbackCompositionEnd(topLevelType, nativeEvent)) { + var chars = currentComposition.getData(); + FallbackCompositionState.release(currentComposition); + currentComposition = null; + return chars; + } + return null; + } + + switch (topLevelType) { + case topLevelTypes.topPaste: + // If a paste event occurs after a keypress, throw out the input + // chars. Paste events should not lead to BeforeInput events. + return null; + case topLevelTypes.topKeyPress: + /** + * As of v27, Firefox may fire keypress events even when no character + * will be inserted. A few possibilities: + * + * - `which` is `0`. Arrow keys, Esc key, etc. + * + * - `which` is the pressed key code, but no char is available. + * Ex: 'AltGr + d` in Polish. There is no modified character for + * this key combination and no character is inserted into the + * document, but FF fires the keypress for char code `100` anyway. + * No `input` event will occur. + * + * - `which` is the pressed key code, but a command combination is + * being used. Ex: `Cmd+C`. No character is inserted, and no + * `input` event will occur. + */ + if (nativeEvent.which && !isKeypressCommand(nativeEvent)) { + return String.fromCharCode(nativeEvent.which); + } + return null; + case topLevelTypes.topCompositionEnd: + return useFallbackCompositionData ? null : nativeEvent.data; + default: + return null; + } +} + +/** + * Extract a SyntheticInputEvent for `beforeInput`, based on either native + * `textInput` or fallback behavior. + * + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {?object} A SyntheticInputEvent. + */ +function extractBeforeInputEvent(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) { + var chars; + + if (canUseTextInputEvent) { + chars = getNativeBeforeInputChars(topLevelType, nativeEvent); + } else { + chars = getFallbackBeforeInputChars(topLevelType, nativeEvent); + } + + // If no characters are being inserted, no BeforeInput event should + // be fired. + if (!chars) { + return null; + } + + var event = SyntheticInputEvent.getPooled(eventTypes.beforeInput, topLevelTargetID, nativeEvent, nativeEventTarget); + + event.data = chars; + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; +} + +/** + * Create an `onBeforeInput` event to match + * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents. + * + * This event plugin is based on the native `textInput` event + * available in Chrome, Safari, Opera, and IE. This event fires after + * `onKeyPress` and `onCompositionEnd`, but before `onInput`. + * + * `beforeInput` is spec'd but not implemented in any browsers, and + * the `input` event does not provide any useful information about what has + * actually been added, contrary to the spec. Thus, `textInput` is the best + * available event to identify the characters that have actually been inserted + * into the target node. + * + * This plugin is also responsible for emitting `composition` events, thus + * allowing us to share composition fallback code for both `beforeInput` and + * `composition` event types. + */ +var BeforeInputEventPlugin = { + + eventTypes: eventTypes, + + /** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {*} An accumulation of synthetic events. + * @see {EventPluginHub.extractEvents} + */ + extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) { + return [extractCompositionEvent(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget), extractBeforeInputEvent(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget)]; + } +}; + +module.exports = BeforeInputEventPlugin; +},{"103":103,"107":107,"147":147,"15":15,"166":166,"19":19,"20":20}],4:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CSSProperty + */ + +'use strict'; + +/** + * CSS properties which accept numbers but are not in units of "px". + */ +var isUnitlessNumber = { + animationIterationCount: true, + boxFlex: true, + boxFlexGroup: true, + boxOrdinalGroup: true, + columnCount: true, + flex: true, + flexGrow: true, + flexPositive: true, + flexShrink: true, + flexNegative: true, + flexOrder: true, + fontWeight: true, + lineClamp: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + tabSize: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related properties + fillOpacity: true, + stopOpacity: true, + strokeDashoffset: true, + strokeOpacity: true, + strokeWidth: true +}; + +/** + * @param {string} prefix vendor-specific prefix, eg: Webkit + * @param {string} key style name, eg: transitionDuration + * @return {string} style name prefixed with `prefix`, properly camelCased, eg: + * WebkitTransitionDuration + */ +function prefixKey(prefix, key) { + return prefix + key.charAt(0).toUpperCase() + key.substring(1); +} + +/** + * Support style names that may come passed in prefixed by adding permutations + * of vendor prefixes. + */ +var prefixes = ['Webkit', 'ms', 'Moz', 'O']; + +// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an +// infinite loop, because it iterates over the newly added props too. +Object.keys(isUnitlessNumber).forEach(function (prop) { + prefixes.forEach(function (prefix) { + isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; + }); +}); + +/** + * Most style properties can be unset by doing .style[prop] = '' but IE8 + * doesn't like doing that with shorthand properties so for the properties that + * IE8 breaks on, which are listed here, we instead unset each of the + * individual properties. See http://bugs.jquery.com/ticket/12385. + * The 4-value 'clock' properties like margin, padding, border-width seem to + * behave without any problems. Curiously, list-style works too without any + * special prodding. + */ +var shorthandPropertyExpansions = { + background: { + backgroundAttachment: true, + backgroundColor: true, + backgroundImage: true, + backgroundPositionX: true, + backgroundPositionY: true, + backgroundRepeat: true + }, + backgroundPosition: { + backgroundPositionX: true, + backgroundPositionY: true + }, + border: { + borderWidth: true, + borderStyle: true, + borderColor: true + }, + borderBottom: { + borderBottomWidth: true, + borderBottomStyle: true, + borderBottomColor: true + }, + borderLeft: { + borderLeftWidth: true, + borderLeftStyle: true, + borderLeftColor: true + }, + borderRight: { + borderRightWidth: true, + borderRightStyle: true, + borderRightColor: true + }, + borderTop: { + borderTopWidth: true, + borderTopStyle: true, + borderTopColor: true + }, + font: { + fontStyle: true, + fontVariant: true, + fontWeight: true, + fontSize: true, + lineHeight: true, + fontFamily: true + }, + outline: { + outlineWidth: true, + outlineStyle: true, + outlineColor: true + } +}; + +var CSSProperty = { + isUnitlessNumber: isUnitlessNumber, + shorthandPropertyExpansions: shorthandPropertyExpansions +}; + +module.exports = CSSProperty; +},{}],5:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CSSPropertyOperations + * @typechecks static-only + */ + +'use strict'; + +var CSSProperty = _dereq_(4); +var ExecutionEnvironment = _dereq_(147); +var ReactPerf = _dereq_(78); + +var camelizeStyleName = _dereq_(149); +var dangerousStyleValue = _dereq_(119); +var hyphenateStyleName = _dereq_(160); +var memoizeStringOnly = _dereq_(168); +var warning = _dereq_(173); + +var processStyleName = memoizeStringOnly(function (styleName) { + return hyphenateStyleName(styleName); +}); + +var hasShorthandPropertyBug = false; +var styleFloatAccessor = 'cssFloat'; +if (ExecutionEnvironment.canUseDOM) { + var tempStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style; + try { + // IE8 throws "Invalid argument." if resetting shorthand style properties. + tempStyle.font = ''; + } catch (e) { + hasShorthandPropertyBug = true; + } + // IE8 only supports accessing cssFloat (standard) as styleFloat + if (document.documentElement.style.cssFloat === undefined) { + styleFloatAccessor = 'styleFloat'; + } +} + +if ("development" !== 'production') { + // 'msTransform' is correct, but the other prefixes should be capitalized + var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/; + + // style values shouldn't contain a semicolon + var badStyleValueWithSemicolonPattern = /;\s*$/; + + var warnedStyleNames = {}; + var warnedStyleValues = {}; + + var warnHyphenatedStyleName = function (name) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + "development" !== 'production' ? warning(false, 'Unsupported style property %s. Did you mean %s?', name, camelizeStyleName(name)) : undefined; + }; + + var warnBadVendoredStyleName = function (name) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + "development" !== 'production' ? warning(false, 'Unsupported vendor-prefixed style property %s. Did you mean %s?', name, name.charAt(0).toUpperCase() + name.slice(1)) : undefined; + }; + + var warnStyleValueWithSemicolon = function (name, value) { + if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) { + return; + } + + warnedStyleValues[value] = true; + "development" !== 'production' ? warning(false, 'Style property values shouldn\'t contain a semicolon. ' + 'Try "%s: %s" instead.', name, value.replace(badStyleValueWithSemicolonPattern, '')) : undefined; + }; + + /** + * @param {string} name + * @param {*} value + */ + var warnValidStyle = function (name, value) { + if (name.indexOf('-') > -1) { + warnHyphenatedStyleName(name); + } else if (badVendoredStyleNamePattern.test(name)) { + warnBadVendoredStyleName(name); + } else if (badStyleValueWithSemicolonPattern.test(value)) { + warnStyleValueWithSemicolon(name, value); + } + }; +} + +/** + * Operations for dealing with CSS properties. + */ +var CSSPropertyOperations = { + + /** + * Serializes a mapping of style properties for use as inline styles: + * + * > createMarkupForStyles({width: '200px', height: 0}) + * "width:200px;height:0;" + * + * Undefined values are ignored so that declarative programming is easier. + * The result should be HTML-escaped before insertion into the DOM. + * + * @param {object} styles + * @return {?string} + */ + createMarkupForStyles: function (styles) { + var serialized = ''; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + var styleValue = styles[styleName]; + if ("development" !== 'production') { + warnValidStyle(styleName, styleValue); + } + if (styleValue != null) { + serialized += processStyleName(styleName) + ':'; + serialized += dangerousStyleValue(styleName, styleValue) + ';'; + } + } + return serialized || null; + }, + + /** + * Sets the value for multiple styles on a node. If a value is specified as + * '' (empty string), the corresponding style property will be unset. + * + * @param {DOMElement} node + * @param {object} styles + */ + setValueForStyles: function (node, styles) { + var style = node.style; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + if ("development" !== 'production') { + warnValidStyle(styleName, styles[styleName]); + } + var styleValue = dangerousStyleValue(styleName, styles[styleName]); + if (styleName === 'float') { + styleName = styleFloatAccessor; + } + if (styleValue) { + style[styleName] = styleValue; + } else { + var expansion = hasShorthandPropertyBug && CSSProperty.shorthandPropertyExpansions[styleName]; + if (expansion) { + // Shorthand property that IE8 won't like unsetting, so unset each + // component to placate it + for (var individualStyleName in expansion) { + style[individualStyleName] = ''; + } + } else { + style[styleName] = ''; + } + } + } + } + +}; + +ReactPerf.measureMethods(CSSPropertyOperations, 'CSSPropertyOperations', { + setValueForStyles: 'setValueForStyles' +}); + +module.exports = CSSPropertyOperations; +},{"119":119,"147":147,"149":149,"160":160,"168":168,"173":173,"4":4,"78":78}],6:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CallbackQueue + */ + +'use strict'; + +var PooledClass = _dereq_(25); + +var assign = _dereq_(24); +var invariant = _dereq_(161); + +/** + * A specialized pseudo-event module to help keep track of components waiting to + * be notified when their DOM representations are available for use. + * + * This implements `PooledClass`, so you should never need to instantiate this. + * Instead, use `CallbackQueue.getPooled()`. + * + * @class ReactMountReady + * @implements PooledClass + * @internal + */ +function CallbackQueue() { + this._callbacks = null; + this._contexts = null; +} + +assign(CallbackQueue.prototype, { + + /** + * Enqueues a callback to be invoked when `notifyAll` is invoked. + * + * @param {function} callback Invoked when `notifyAll` is invoked. + * @param {?object} context Context to call `callback` with. + * @internal + */ + enqueue: function (callback, context) { + this._callbacks = this._callbacks || []; + this._contexts = this._contexts || []; + this._callbacks.push(callback); + this._contexts.push(context); + }, + + /** + * Invokes all enqueued callbacks and clears the queue. This is invoked after + * the DOM representation of a component has been created or updated. + * + * @internal + */ + notifyAll: function () { + var callbacks = this._callbacks; + var contexts = this._contexts; + if (callbacks) { + !(callbacks.length === contexts.length) ? "development" !== 'production' ? invariant(false, 'Mismatched list of contexts in callback queue') : invariant(false) : undefined; + this._callbacks = null; + this._contexts = null; + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].call(contexts[i]); + } + callbacks.length = 0; + contexts.length = 0; + } + }, + + /** + * Resets the internal queue. + * + * @internal + */ + reset: function () { + this._callbacks = null; + this._contexts = null; + }, + + /** + * `PooledClass` looks for this. + */ + destructor: function () { + this.reset(); + } + +}); + +PooledClass.addPoolingTo(CallbackQueue); + +module.exports = CallbackQueue; +},{"161":161,"24":24,"25":25}],7:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ChangeEventPlugin + */ + +'use strict'; + +var EventConstants = _dereq_(15); +var EventPluginHub = _dereq_(16); +var EventPropagators = _dereq_(19); +var ExecutionEnvironment = _dereq_(147); +var ReactUpdates = _dereq_(96); +var SyntheticEvent = _dereq_(105); + +var getEventTarget = _dereq_(128); +var isEventSupported = _dereq_(133); +var isTextInputElement = _dereq_(134); +var keyOf = _dereq_(166); + +var topLevelTypes = EventConstants.topLevelTypes; + +var eventTypes = { + change: { + phasedRegistrationNames: { + bubbled: keyOf({ onChange: null }), + captured: keyOf({ onChangeCapture: null }) + }, + dependencies: [topLevelTypes.topBlur, topLevelTypes.topChange, topLevelTypes.topClick, topLevelTypes.topFocus, topLevelTypes.topInput, topLevelTypes.topKeyDown, topLevelTypes.topKeyUp, topLevelTypes.topSelectionChange] + } +}; + +/** + * For IE shims + */ +var activeElement = null; +var activeElementID = null; +var activeElementValue = null; +var activeElementValueProp = null; + +/** + * SECTION: handle `change` event + */ +function shouldUseChangeEvent(elem) { + var nodeName = elem.nodeName && elem.nodeName.toLowerCase(); + return nodeName === 'select' || nodeName === 'input' && elem.type === 'file'; +} + +var doesChangeEventBubble = false; +if (ExecutionEnvironment.canUseDOM) { + // See `handleChange` comment below + doesChangeEventBubble = isEventSupported('change') && (!('documentMode' in document) || document.documentMode > 8); +} + +function manualDispatchChangeEvent(nativeEvent) { + var event = SyntheticEvent.getPooled(eventTypes.change, activeElementID, nativeEvent, getEventTarget(nativeEvent)); + EventPropagators.accumulateTwoPhaseDispatches(event); + + // If change and propertychange bubbled, we'd just bind to it like all the + // other events and have it go through ReactBrowserEventEmitter. Since it + // doesn't, we manually listen for the events and so we have to enqueue and + // process the abstract event manually. + // + // Batching is necessary here in order to ensure that all event handlers run + // before the next rerender (including event handlers attached to ancestor + // elements instead of directly on the input). Without this, controlled + // components don't work properly in conjunction with event bubbling because + // the component is rerendered and the value reverted before all the event + // handlers can run. See https://github.com/facebook/react/issues/708. + ReactUpdates.batchedUpdates(runEventInBatch, event); +} + +function runEventInBatch(event) { + EventPluginHub.enqueueEvents(event); + EventPluginHub.processEventQueue(false); +} + +function startWatchingForChangeEventIE8(target, targetID) { + activeElement = target; + activeElementID = targetID; + activeElement.attachEvent('onchange', manualDispatchChangeEvent); +} + +function stopWatchingForChangeEventIE8() { + if (!activeElement) { + return; + } + activeElement.detachEvent('onchange', manualDispatchChangeEvent); + activeElement = null; + activeElementID = null; +} + +function getTargetIDForChangeEvent(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topChange) { + return topLevelTargetID; + } +} +function handleEventsForChangeEventIE8(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topFocus) { + // stopWatching() should be a noop here but we call it just in case we + // missed a blur event somehow. + stopWatchingForChangeEventIE8(); + startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID); + } else if (topLevelType === topLevelTypes.topBlur) { + stopWatchingForChangeEventIE8(); + } +} + +/** + * SECTION: handle `input` event + */ +var isInputEventSupported = false; +if (ExecutionEnvironment.canUseDOM) { + // IE9 claims to support the input event but fails to trigger it when + // deleting text, so we ignore its input events + isInputEventSupported = isEventSupported('input') && (!('documentMode' in document) || document.documentMode > 9); +} + +/** + * (For old IE.) Replacement getter/setter for the `value` property that gets + * set on the active element. + */ +var newValueProp = { + get: function () { + return activeElementValueProp.get.call(this); + }, + set: function (val) { + // Cast to a string so we can do equality checks. + activeElementValue = '' + val; + activeElementValueProp.set.call(this, val); + } +}; + +/** + * (For old IE.) Starts tracking propertychange events on the passed-in element + * and override the value property so that we can distinguish user events from + * value changes in JS. + */ +function startWatchingForValueChange(target, targetID) { + activeElement = target; + activeElementID = targetID; + activeElementValue = target.value; + activeElementValueProp = Object.getOwnPropertyDescriptor(target.constructor.prototype, 'value'); + + // Not guarded in a canDefineProperty check: IE8 supports defineProperty only + // on DOM elements + Object.defineProperty(activeElement, 'value', newValueProp); + activeElement.attachEvent('onpropertychange', handlePropertyChange); +} + +/** + * (For old IE.) Removes the event listeners from the currently-tracked element, + * if any exists. + */ +function stopWatchingForValueChange() { + if (!activeElement) { + return; + } + + // delete restores the original property definition + delete activeElement.value; + activeElement.detachEvent('onpropertychange', handlePropertyChange); + + activeElement = null; + activeElementID = null; + activeElementValue = null; + activeElementValueProp = null; +} + +/** + * (For old IE.) Handles a propertychange event, sending a `change` event if + * the value of the active element has changed. + */ +function handlePropertyChange(nativeEvent) { + if (nativeEvent.propertyName !== 'value') { + return; + } + var value = nativeEvent.srcElement.value; + if (value === activeElementValue) { + return; + } + activeElementValue = value; + + manualDispatchChangeEvent(nativeEvent); +} + +/** + * If a `change` event should be fired, returns the target's ID. + */ +function getTargetIDForInputEvent(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topInput) { + // In modern browsers (i.e., not IE8 or IE9), the input event is exactly + // what we want so fall through here and trigger an abstract event + return topLevelTargetID; + } +} + +// For IE8 and IE9. +function handleEventsForInputEventIE(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topFocus) { + // In IE8, we can capture almost all .value changes by adding a + // propertychange handler and looking for events with propertyName + // equal to 'value' + // In IE9, propertychange fires for most input events but is buggy and + // doesn't fire when text is deleted, but conveniently, selectionchange + // appears to fire in all of the remaining cases so we catch those and + // forward the event if the value has changed + // In either case, we don't want to call the event handler if the value + // is changed from JS so we redefine a setter for `.value` that updates + // our activeElementValue variable, allowing us to ignore those changes + // + // stopWatching() should be a noop here but we call it just in case we + // missed a blur event somehow. + stopWatchingForValueChange(); + startWatchingForValueChange(topLevelTarget, topLevelTargetID); + } else if (topLevelType === topLevelTypes.topBlur) { + stopWatchingForValueChange(); + } +} + +// For IE8 and IE9. +function getTargetIDForInputEventIE(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topSelectionChange || topLevelType === topLevelTypes.topKeyUp || topLevelType === topLevelTypes.topKeyDown) { + // On the selectionchange event, the target is just document which isn't + // helpful for us so just check activeElement instead. + // + // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire + // propertychange on the first input event after setting `value` from a + // script and fires only keydown, keypress, keyup. Catching keyup usually + // gets it and catching keydown lets us fire an event for the first + // keystroke if user does a key repeat (it'll be a little delayed: right + // before the second keystroke). Other input methods (e.g., paste) seem to + // fire selectionchange normally. + if (activeElement && activeElement.value !== activeElementValue) { + activeElementValue = activeElement.value; + return activeElementID; + } + } +} + +/** + * SECTION: handle `click` event + */ +function shouldUseClickEvent(elem) { + // Use the `click` event to detect changes to checkbox and radio inputs. + // This approach works across all browsers, whereas `change` does not fire + // until `blur` in IE8. + return elem.nodeName && elem.nodeName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio'); +} + +function getTargetIDForClickEvent(topLevelType, topLevelTarget, topLevelTargetID) { + if (topLevelType === topLevelTypes.topClick) { + return topLevelTargetID; + } +} + +/** + * This plugin creates an `onChange` event that normalizes change events + * across form elements. This event fires at a time when it's possible to + * change the element's value without seeing a flicker. + * + * Supported elements are: + * - input (see `isTextInputElement`) + * - textarea + * - select + */ +var ChangeEventPlugin = { + + eventTypes: eventTypes, + + /** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {*} An accumulation of synthetic events. + * @see {EventPluginHub.extractEvents} + */ + extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) { + + var getTargetIDFunc, handleEventFunc; + if (shouldUseChangeEvent(topLevelTarget)) { + if (doesChangeEventBubble) { + getTargetIDFunc = getTargetIDForChangeEvent; + } else { + handleEventFunc = handleEventsForChangeEventIE8; + } + } else if (isTextInputElement(topLevelTarget)) { + if (isInputEventSupported) { + getTargetIDFunc = getTargetIDForInputEvent; + } else { + getTargetIDFunc = getTargetIDForInputEventIE; + handleEventFunc = handleEventsForInputEventIE; + } + } else if (shouldUseClickEvent(topLevelTarget)) { + getTargetIDFunc = getTargetIDForClickEvent; + } + + if (getTargetIDFunc) { + var targetID = getTargetIDFunc(topLevelType, topLevelTarget, topLevelTargetID); + if (targetID) { + var event = SyntheticEvent.getPooled(eventTypes.change, targetID, nativeEvent, nativeEventTarget); + event.type = 'change'; + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; + } + } + + if (handleEventFunc) { + handleEventFunc(topLevelType, topLevelTarget, topLevelTargetID); + } + } + +}; + +module.exports = ChangeEventPlugin; +},{"105":105,"128":128,"133":133,"134":134,"147":147,"15":15,"16":16,"166":166,"19":19,"96":96}],8:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ClientReactRootIndex + * @typechecks + */ + +'use strict'; + +var nextReactRootIndex = 0; + +var ClientReactRootIndex = { + createReactRootIndex: function () { + return nextReactRootIndex++; + } +}; + +module.exports = ClientReactRootIndex; +},{}],9:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMChildrenOperations + * @typechecks static-only + */ + +'use strict'; + +var Danger = _dereq_(12); +var ReactMultiChildUpdateTypes = _dereq_(74); +var ReactPerf = _dereq_(78); + +var setInnerHTML = _dereq_(138); +var setTextContent = _dereq_(139); +var invariant = _dereq_(161); + +/** + * Inserts `childNode` as a child of `parentNode` at the `index`. + * + * @param {DOMElement} parentNode Parent node in which to insert. + * @param {DOMElement} childNode Child node to insert. + * @param {number} index Index at which to insert the child. + * @internal + */ +function insertChildAt(parentNode, childNode, index) { + // By exploiting arrays returning `undefined` for an undefined index, we can + // rely exclusively on `insertBefore(node, null)` instead of also using + // `appendChild(node)`. However, using `undefined` is not allowed by all + // browsers so we must replace it with `null`. + + // fix render order error in safari + // IE8 will throw error when index out of list size. + var beforeChild = index >= parentNode.childNodes.length ? null : parentNode.childNodes.item(index); + + parentNode.insertBefore(childNode, beforeChild); +} + +/** + * Operations for updating with DOM children. + */ +var DOMChildrenOperations = { + + dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, + + updateTextContent: setTextContent, + + /** + * Updates a component's children by processing a series of updates. The + * update configurations are each expected to have a `parentNode` property. + * + * @param {array} updates List of update configurations. + * @param {array} markupList List of markup strings. + * @internal + */ + processUpdates: function (updates, markupList) { + var update; + // Mapping from parent IDs to initial child orderings. + var initialChildren = null; + // List of children that will be moved or removed. + var updatedChildren = null; + + for (var i = 0; i < updates.length; i++) { + update = updates[i]; + if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING || update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { + var updatedIndex = update.fromIndex; + var updatedChild = update.parentNode.childNodes[updatedIndex]; + var parentID = update.parentID; + + !updatedChild ? "development" !== 'production' ? invariant(false, 'processUpdates(): Unable to find child %s of element. This ' + 'probably means the DOM was unexpectedly mutated (e.g., by the ' + 'browser), usually due to forgetting a when using tables, ' + 'nesting tags like
,

, or , or using non-SVG elements ' + 'in an parent. Try inspecting the child nodes of the element ' + 'with React ID `%s`.', updatedIndex, parentID) : invariant(false) : undefined; + + initialChildren = initialChildren || {}; + initialChildren[parentID] = initialChildren[parentID] || []; + initialChildren[parentID][updatedIndex] = updatedChild; + + updatedChildren = updatedChildren || []; + updatedChildren.push(updatedChild); + } + } + + var renderedMarkup; + // markupList is either a list of markup or just a list of elements + if (markupList.length && typeof markupList[0] === 'string') { + renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); + } else { + renderedMarkup = markupList; + } + + // Remove updated children first so that `toIndex` is consistent. + if (updatedChildren) { + for (var j = 0; j < updatedChildren.length; j++) { + updatedChildren[j].parentNode.removeChild(updatedChildren[j]); + } + } + + for (var k = 0; k < updates.length; k++) { + update = updates[k]; + switch (update.type) { + case ReactMultiChildUpdateTypes.INSERT_MARKUP: + insertChildAt(update.parentNode, renderedMarkup[update.markupIndex], update.toIndex); + break; + case ReactMultiChildUpdateTypes.MOVE_EXISTING: + insertChildAt(update.parentNode, initialChildren[update.parentID][update.fromIndex], update.toIndex); + break; + case ReactMultiChildUpdateTypes.SET_MARKUP: + setInnerHTML(update.parentNode, update.content); + break; + case ReactMultiChildUpdateTypes.TEXT_CONTENT: + setTextContent(update.parentNode, update.content); + break; + case ReactMultiChildUpdateTypes.REMOVE_NODE: + // Already removed by the for-loop above. + break; + } + } + } + +}; + +ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', { + updateTextContent: 'updateTextContent' +}); + +module.exports = DOMChildrenOperations; +},{"12":12,"138":138,"139":139,"161":161,"74":74,"78":78}],10:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMProperty + * @typechecks static-only + */ + +'use strict'; + +var invariant = _dereq_(161); + +function checkMask(value, bitmask) { + return (value & bitmask) === bitmask; +} + +var DOMPropertyInjection = { + /** + * Mapping from normalized, camelcased property names to a configuration that + * specifies how the associated DOM property should be accessed or rendered. + */ + MUST_USE_ATTRIBUTE: 0x1, + MUST_USE_PROPERTY: 0x2, + HAS_SIDE_EFFECTS: 0x4, + HAS_BOOLEAN_VALUE: 0x8, + HAS_NUMERIC_VALUE: 0x10, + HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, + HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, + + /** + * Inject some specialized knowledge about the DOM. This takes a config object + * with the following properties: + * + * isCustomAttribute: function that given an attribute name will return true + * if it can be inserted into the DOM verbatim. Useful for data-* or aria-* + * attributes where it's impossible to enumerate all of the possible + * attribute names, + * + * Properties: object mapping DOM property name to one of the + * DOMPropertyInjection constants or null. If your attribute isn't in here, + * it won't get written to the DOM. + * + * DOMAttributeNames: object mapping React attribute name to the DOM + * attribute name. Attribute names not specified use the **lowercase** + * normalized name. + * + * DOMAttributeNamespaces: object mapping React attribute name to the DOM + * attribute namespace URL. (Attribute names not specified use no namespace.) + * + * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. + * Property names not specified use the normalized name. + * + * DOMMutationMethods: Properties that require special mutation methods. If + * `value` is undefined, the mutation method should unset the property. + * + * @param {object} domPropertyConfig the config as described above. + */ + injectDOMPropertyConfig: function (domPropertyConfig) { + var Injection = DOMPropertyInjection; + var Properties = domPropertyConfig.Properties || {}; + var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {}; + var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; + var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; + var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; + + if (domPropertyConfig.isCustomAttribute) { + DOMProperty._isCustomAttributeFunctions.push(domPropertyConfig.isCustomAttribute); + } + + for (var propName in Properties) { + !!DOMProperty.properties.hasOwnProperty(propName) ? "development" !== 'production' ? invariant(false, 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + '\'%s\' which has already been injected. You may be accidentally ' + 'injecting the same DOM property config twice, or you may be ' + 'injecting two configs that have conflicting property names.', propName) : invariant(false) : undefined; + + var lowerCased = propName.toLowerCase(); + var propConfig = Properties[propName]; + + var propertyInfo = { + attributeName: lowerCased, + attributeNamespace: null, + propertyName: propName, + mutationMethod: null, + + mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE), + mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY), + hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS), + hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE), + hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE), + hasPositiveNumericValue: checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE), + hasOverloadedBooleanValue: checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE) + }; + + !(!propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty) ? "development" !== 'production' ? invariant(false, 'DOMProperty: Cannot require using both attribute and property: %s', propName) : invariant(false) : undefined; + !(propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects) ? "development" !== 'production' ? invariant(false, 'DOMProperty: Properties that have side effects must use property: %s', propName) : invariant(false) : undefined; + !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? "development" !== 'production' ? invariant(false, 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + 'numeric value, but not a combination: %s', propName) : invariant(false) : undefined; + + if ("development" !== 'production') { + DOMProperty.getPossibleStandardName[lowerCased] = propName; + } + + if (DOMAttributeNames.hasOwnProperty(propName)) { + var attributeName = DOMAttributeNames[propName]; + propertyInfo.attributeName = attributeName; + if ("development" !== 'production') { + DOMProperty.getPossibleStandardName[attributeName] = propName; + } + } + + if (DOMAttributeNamespaces.hasOwnProperty(propName)) { + propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName]; + } + + if (DOMPropertyNames.hasOwnProperty(propName)) { + propertyInfo.propertyName = DOMPropertyNames[propName]; + } + + if (DOMMutationMethods.hasOwnProperty(propName)) { + propertyInfo.mutationMethod = DOMMutationMethods[propName]; + } + + DOMProperty.properties[propName] = propertyInfo; + } + } +}; +var defaultValueCache = {}; + +/** + * DOMProperty exports lookup objects that can be used like functions: + * + * > DOMProperty.isValid['id'] + * true + * > DOMProperty.isValid['foobar'] + * undefined + * + * Although this may be confusing, it performs better in general. + * + * @see http://jsperf.com/key-exists + * @see http://jsperf.com/key-missing + */ +var DOMProperty = { + + ID_ATTRIBUTE_NAME: 'data-reactid', + + /** + * Map from property "standard name" to an object with info about how to set + * the property in the DOM. Each object contains: + * + * attributeName: + * Used when rendering markup or with `*Attribute()`. + * attributeNamespace + * propertyName: + * Used on DOM node instances. (This includes properties that mutate due to + * external factors.) + * mutationMethod: + * If non-null, used instead of the property or `setAttribute()` after + * initial render. + * mustUseAttribute: + * Whether the property must be accessed and mutated using `*Attribute()`. + * (This includes anything that fails ` in `.) + * mustUseProperty: + * Whether the property must be accessed and mutated as an object property. + * hasSideEffects: + * Whether or not setting a value causes side effects such as triggering + * resources to be loaded or text selection changes. If true, we read from + * the DOM before updating to ensure that the value is only set if it has + * changed. + * hasBooleanValue: + * Whether the property should be removed when set to a falsey value. + * hasNumericValue: + * Whether the property must be numeric or parse as a numeric and should be + * removed when set to a falsey value. + * hasPositiveNumericValue: + * Whether the property must be positive numeric or parse as a positive + * numeric and should be removed when set to a falsey value. + * hasOverloadedBooleanValue: + * Whether the property can be used as a flag as well as with a value. + * Removed when strictly equal to false; present without a value when + * strictly equal to true; present with a value otherwise. + */ + properties: {}, + + /** + * Mapping from lowercase property names to the properly cased version, used + * to warn in the case of missing properties. Available only in __DEV__. + * @type {Object} + */ + getPossibleStandardName: "development" !== 'production' ? {} : null, + + /** + * All of the isCustomAttribute() functions that have been injected. + */ + _isCustomAttributeFunctions: [], + + /** + * Checks whether a property name is a custom attribute. + * @method + */ + isCustomAttribute: function (attributeName) { + for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { + var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; + if (isCustomAttributeFn(attributeName)) { + return true; + } + } + return false; + }, + + /** + * Returns the default property value for a DOM property (i.e., not an + * attribute). Most default values are '' or false, but not all. Worse yet, + * some (in particular, `type`) vary depending on the type of element. + * + * TODO: Is it better to grab all the possible properties when creating an + * element to avoid having to create the same element twice? + */ + getDefaultValueForProperty: function (nodeName, prop) { + var nodeDefaults = defaultValueCache[nodeName]; + var testElement; + if (!nodeDefaults) { + defaultValueCache[nodeName] = nodeDefaults = {}; + } + if (!(prop in nodeDefaults)) { + testElement = document.createElementNS('http://www.w3.org/1999/xhtml', nodeName); + nodeDefaults[prop] = testElement[prop]; + } + return nodeDefaults[prop]; + }, + + injection: DOMPropertyInjection +}; + +module.exports = DOMProperty; +},{"161":161}],11:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMPropertyOperations + * @typechecks static-only + */ + +'use strict'; + +var DOMProperty = _dereq_(10); +var ReactPerf = _dereq_(78); + +var quoteAttributeValueForBrowser = _dereq_(136); +var warning = _dereq_(173); + +// Simplified subset +var VALID_ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][\w\.\-]*$/; +var illegalAttributeNameCache = {}; +var validatedAttributeNameCache = {}; + +function isAttributeNameSafe(attributeName) { + if (validatedAttributeNameCache.hasOwnProperty(attributeName)) { + return true; + } + if (illegalAttributeNameCache.hasOwnProperty(attributeName)) { + return false; + } + if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { + validatedAttributeNameCache[attributeName] = true; + return true; + } + illegalAttributeNameCache[attributeName] = true; + "development" !== 'production' ? warning(false, 'Invalid attribute name: `%s`', attributeName) : undefined; + return false; +} + +function shouldIgnoreValue(propertyInfo, value) { + return value == null || propertyInfo.hasBooleanValue && !value || propertyInfo.hasNumericValue && isNaN(value) || propertyInfo.hasPositiveNumericValue && value < 1 || propertyInfo.hasOverloadedBooleanValue && value === false; +} + +if ("development" !== 'production') { + var reactProps = { + children: true, + dangerouslySetInnerHTML: true, + key: true, + ref: true + }; + var warnedProperties = {}; + + var warnUnknownProperty = function (name) { + if (reactProps.hasOwnProperty(name) && reactProps[name] || warnedProperties.hasOwnProperty(name) && warnedProperties[name]) { + return; + } + + warnedProperties[name] = true; + var lowerCasedName = name.toLowerCase(); + + // data-* attributes should be lowercase; suggest the lowercase version + var standardName = DOMProperty.isCustomAttribute(lowerCasedName) ? lowerCasedName : DOMProperty.getPossibleStandardName.hasOwnProperty(lowerCasedName) ? DOMProperty.getPossibleStandardName[lowerCasedName] : null; + + // For now, only warn when we have a suggested correction. This prevents + // logging too much when using transferPropsTo. + "development" !== 'production' ? warning(standardName == null, 'Unknown DOM property %s. Did you mean %s?', name, standardName) : undefined; + }; +} + +/** + * Operations for dealing with DOM properties. + */ +var DOMPropertyOperations = { + + /** + * Creates markup for the ID property. + * + * @param {string} id Unescaped ID. + * @return {string} Markup string. + */ + createMarkupForID: function (id) { + return DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id); + }, + + setAttributeForID: function (node, id) { + node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id); + }, + + /** + * Creates markup for a property. + * + * @param {string} name + * @param {*} value + * @return {?string} Markup string, or null if the property was invalid. + */ + createMarkupForProperty: function (name, value) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + if (shouldIgnoreValue(propertyInfo, value)) { + return ''; + } + var attributeName = propertyInfo.attributeName; + if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { + return attributeName + '=""'; + } + return attributeName + '=' + quoteAttributeValueForBrowser(value); + } else if (DOMProperty.isCustomAttribute(name)) { + if (value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); + } else if ("development" !== 'production') { + warnUnknownProperty(name); + } + return null; + }, + + /** + * Creates markup for a custom property. + * + * @param {string} name + * @param {*} value + * @return {string} Markup string, or empty string if the property was invalid. + */ + createMarkupForCustomAttribute: function (name, value) { + if (!isAttributeNameSafe(name) || value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); + }, + + /** + * Sets the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + * @param {*} value + */ + setValueForProperty: function (node, name, value) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + var mutationMethod = propertyInfo.mutationMethod; + if (mutationMethod) { + mutationMethod(node, value); + } else if (shouldIgnoreValue(propertyInfo, value)) { + this.deleteValueForProperty(node, name); + } else if (propertyInfo.mustUseAttribute) { + var attributeName = propertyInfo.attributeName; + var namespace = propertyInfo.attributeNamespace; + // `setAttribute` with objects becomes only `[object]` in IE8/9, + // ('' + value) makes it output the correct toString()-value. + if (namespace) { + node.setAttributeNS(namespace, attributeName, '' + value); + } else if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { + node.setAttribute(attributeName, ''); + } else { + node.setAttribute(attributeName, '' + value); + } + } else { + var propName = propertyInfo.propertyName; + // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the + // property type before comparing; only `value` does and is string. + if (!propertyInfo.hasSideEffects || '' + node[propName] !== '' + value) { + // Contrary to `setAttribute`, object properties are properly + // `toString`ed by IE8/9. + node[propName] = value; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + DOMPropertyOperations.setValueForAttribute(node, name, value); + } else if ("development" !== 'production') { + warnUnknownProperty(name); + } + }, + + setValueForAttribute: function (node, name, value) { + if (!isAttributeNameSafe(name)) { + return; + } + if (value == null) { + node.removeAttribute(name); + } else { + node.setAttribute(name, '' + value); + } + }, + + /** + * Deletes the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + */ + deleteValueForProperty: function (node, name) { + var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; + if (propertyInfo) { + var mutationMethod = propertyInfo.mutationMethod; + if (mutationMethod) { + mutationMethod(node, undefined); + } else if (propertyInfo.mustUseAttribute) { + node.removeAttribute(propertyInfo.attributeName); + } else { + var propName = propertyInfo.propertyName; + var defaultValue = DOMProperty.getDefaultValueForProperty(node.nodeName, propName); + if (!propertyInfo.hasSideEffects || '' + node[propName] !== defaultValue) { + node[propName] = defaultValue; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + node.removeAttribute(name); + } else if ("development" !== 'production') { + warnUnknownProperty(name); + } + } + +}; + +ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', { + setValueForProperty: 'setValueForProperty', + setValueForAttribute: 'setValueForAttribute', + deleteValueForProperty: 'deleteValueForProperty' +}); + +module.exports = DOMPropertyOperations; +},{"10":10,"136":136,"173":173,"78":78}],12:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Danger + * @typechecks static-only + */ + +'use strict'; + +var ExecutionEnvironment = _dereq_(147); + +var createNodesFromMarkup = _dereq_(152); +var emptyFunction = _dereq_(153); +var getMarkupWrap = _dereq_(157); +var invariant = _dereq_(161); + +var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; +var RESULT_INDEX_ATTR = 'data-danger-index'; + +/** + * Extracts the `nodeName` from a string of markup. + * + * NOTE: Extracting the `nodeName` does not require a regular expression match + * because we make assumptions about React-generated markup (i.e. there are no + * spaces surrounding the opening tag and there is at least one attribute). + * + * @param {string} markup String of markup. + * @return {string} Node name of the supplied markup. + * @see http://jsperf.com/extract-nodename + */ +function getNodeName(markup) { + return markup.substring(1, markup.indexOf(' ')); +} + +var Danger = { + + /** + * Renders markup into an array of nodes. The markup is expected to render + * into a list of root nodes. Also, the length of `resultList` and + * `markupList` should be the same. + * + * @param {array} markupList List of markup strings to render. + * @return {array} List of rendered nodes. + * @internal + */ + dangerouslyRenderMarkup: function (markupList) { + !ExecutionEnvironment.canUseDOM ? "development" !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + 'thread. Make sure `window` and `document` are available globally ' + 'before requiring React when unit testing or use ' + 'ReactDOMServer.renderToString for server rendering.') : invariant(false) : undefined; + var nodeName; + var markupByNodeName = {}; + // Group markup by `nodeName` if a wrap is necessary, else by '*'. + for (var i = 0; i < markupList.length; i++) { + !markupList[i] ? "development" !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Missing markup.') : invariant(false) : undefined; + nodeName = getNodeName(markupList[i]); + nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; + markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; + markupByNodeName[nodeName][i] = markupList[i]; + } + var resultList = []; + var resultListAssignmentCount = 0; + for (nodeName in markupByNodeName) { + if (!markupByNodeName.hasOwnProperty(nodeName)) { + continue; + } + var markupListByNodeName = markupByNodeName[nodeName]; + + // This for-in loop skips the holes of the sparse array. The order of + // iteration should follow the order of assignment, which happens to match + // numerical index order, but we don't rely on that. + var resultIndex; + for (resultIndex in markupListByNodeName) { + if (markupListByNodeName.hasOwnProperty(resultIndex)) { + var markup = markupListByNodeName[resultIndex]; + + // Push the requested markup with an additional RESULT_INDEX_ATTR + // attribute. If the markup does not start with a < character, it + // will be discarded below (with an appropriate console.error). + markupListByNodeName[resultIndex] = markup.replace(OPEN_TAG_NAME_EXP, + // This index will be parsed back out below. + '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" '); + } + } + + // Render each group of markup with similar wrapping `nodeName`. + var renderNodes = createNodesFromMarkup(markupListByNodeName.join(''), emptyFunction // Do nothing special with

; + * } + * }); + * + * The class specification supports a specific protocol of methods that have + * special meaning (e.g. `render`). See `ReactClassInterface` for + * more the comprehensive protocol. Any other properties and methods in the + * class specification will be available on the prototype. + * + * @interface ReactClassInterface + * @internal + */ +var ReactClassInterface = { + + /** + * An array of Mixin objects to include when defining your component. + * + * @type {array} + * @optional + */ + mixins: SpecPolicy.DEFINE_MANY, + + /** + * An object containing properties and methods that should be defined on + * the component's constructor instead of its prototype (static methods). + * + * @type {object} + * @optional + */ + statics: SpecPolicy.DEFINE_MANY, + + /** + * Definition of prop types for this component. + * + * @type {object} + * @optional + */ + propTypes: SpecPolicy.DEFINE_MANY, + + /** + * Definition of context types for this component. + * + * @type {object} + * @optional + */ + contextTypes: SpecPolicy.DEFINE_MANY, + + /** + * Definition of context types this component sets for its children. + * + * @type {object} + * @optional + */ + childContextTypes: SpecPolicy.DEFINE_MANY, + + // ==== Definition methods ==== + + /** + * Invoked when the component is mounted. Values in the mapping will be set on + * `this.props` if that prop is not specified (i.e. using an `in` check). + * + * This method is invoked before `getInitialState` and therefore cannot rely + * on `this.state` or use `this.setState`. + * + * @return {object} + * @optional + */ + getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * Invoked once before the component is mounted. The return value will be used + * as the initial value of `this.state`. + * + * getInitialState: function() { + * return { + * isOn: false, + * fooBaz: new BazFoo() + * } + * } + * + * @return {object} + * @optional + */ + getInitialState: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * @return {object} + * @optional + */ + getChildContext: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * Uses props from `this.props` and state from `this.state` to render the + * structure of the component. + * + * No guarantees are made about when or how often this method is invoked, so + * it must not have side effects. + * + * render: function() { + * var name = this.props.name; + * return
Hello, {name}!
; + * } + * + * @return {ReactComponent} + * @nosideeffects + * @required + */ + render: SpecPolicy.DEFINE_ONCE, + + // ==== Delegate methods ==== + + /** + * Invoked when the component is initially created and about to be mounted. + * This may have side effects, but any external subscriptions or data created + * by this method must be cleaned up in `componentWillUnmount`. + * + * @optional + */ + componentWillMount: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component has been mounted and has a DOM representation. + * However, there is no guarantee that the DOM node is in the document. + * + * Use this as an opportunity to operate on the DOM when the component has + * been mounted (initialized and rendered) for the first time. + * + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidMount: SpecPolicy.DEFINE_MANY, + + /** + * Invoked before the component receives new props. + * + * Use this as an opportunity to react to a prop transition by updating the + * state using `this.setState`. Current props are accessed via `this.props`. + * + * componentWillReceiveProps: function(nextProps, nextContext) { + * this.setState({ + * likesIncreasing: nextProps.likeCount > this.props.likeCount + * }); + * } + * + * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop + * transition may cause a state change, but the opposite is not true. If you + * need it, you are probably looking for `componentWillUpdate`. + * + * @param {object} nextProps + * @optional + */ + componentWillReceiveProps: SpecPolicy.DEFINE_MANY, + + /** + * Invoked while deciding if the component should be updated as a result of + * receiving new props, state and/or context. + * + * Use this as an opportunity to `return false` when you're certain that the + * transition to the new props/state/context will not require a component + * update. + * + * shouldComponentUpdate: function(nextProps, nextState, nextContext) { + * return !equal(nextProps, this.props) || + * !equal(nextState, this.state) || + * !equal(nextContext, this.context); + * } + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @return {boolean} True if the component should update. + * @optional + */ + shouldComponentUpdate: SpecPolicy.DEFINE_ONCE, + + /** + * Invoked when the component is about to update due to a transition from + * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` + * and `nextContext`. + * + * Use this as an opportunity to perform preparation before an update occurs. + * + * NOTE: You **cannot** use `this.setState()` in this method. + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @param {ReactReconcileTransaction} transaction + * @optional + */ + componentWillUpdate: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component's DOM representation has been updated. + * + * Use this as an opportunity to operate on the DOM when the component has + * been updated. + * + * @param {object} prevProps + * @param {?object} prevState + * @param {?object} prevContext + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidUpdate: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component is about to be removed from its parent and have + * its DOM representation destroyed. + * + * Use this as an opportunity to deallocate any external resources. + * + * NOTE: There is no `componentDidUnmount` since your component will have been + * destroyed by that point. + * + * @optional + */ + componentWillUnmount: SpecPolicy.DEFINE_MANY, + + // ==== Advanced methods ==== + + /** + * Updates the component's currently mounted DOM representation. + * + * By default, this implements React's rendering and reconciliation algorithm. + * Sophisticated clients may wish to override this. + * + * @param {ReactReconcileTransaction} transaction + * @internal + * @overridable + */ + updateComponent: SpecPolicy.OVERRIDE_BASE + +}; + +/** + * Mapping from class specification keys to special processing functions. + * + * Although these are declared like instance properties in the specification + * when defining classes using `React.createClass`, they are actually static + * and are accessible on the constructor instead of the prototype. Despite + * being static, they must be defined outside of the "statics" key under + * which all other static methods are defined. + */ +var RESERVED_SPEC_KEYS = { + displayName: function (Constructor, displayName) { + Constructor.displayName = displayName; + }, + mixins: function (Constructor, mixins) { + if (mixins) { + for (var i = 0; i < mixins.length; i++) { + mixSpecIntoComponent(Constructor, mixins[i]); + } + } + }, + childContextTypes: function (Constructor, childContextTypes) { + if ("development" !== 'production') { + validateTypeDef(Constructor, childContextTypes, ReactPropTypeLocations.childContext); + } + Constructor.childContextTypes = assign({}, Constructor.childContextTypes, childContextTypes); + }, + contextTypes: function (Constructor, contextTypes) { + if ("development" !== 'production') { + validateTypeDef(Constructor, contextTypes, ReactPropTypeLocations.context); + } + Constructor.contextTypes = assign({}, Constructor.contextTypes, contextTypes); + }, + /** + * Special case getDefaultProps which should move into statics but requires + * automatic merging. + */ + getDefaultProps: function (Constructor, getDefaultProps) { + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps = createMergedResultFunction(Constructor.getDefaultProps, getDefaultProps); + } else { + Constructor.getDefaultProps = getDefaultProps; + } + }, + propTypes: function (Constructor, propTypes) { + if ("development" !== 'production') { + validateTypeDef(Constructor, propTypes, ReactPropTypeLocations.prop); + } + Constructor.propTypes = assign({}, Constructor.propTypes, propTypes); + }, + statics: function (Constructor, statics) { + mixStaticSpecIntoComponent(Constructor, statics); + }, + autobind: function () {} }; + +// noop +function validateTypeDef(Constructor, typeDef, location) { + for (var propName in typeDef) { + if (typeDef.hasOwnProperty(propName)) { + // use a warning instead of an invariant so components + // don't show up in prod but not in __DEV__ + "development" !== 'production' ? warning(typeof typeDef[propName] === 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'React.PropTypes.', Constructor.displayName || 'ReactClass', ReactPropTypeLocationNames[location], propName) : undefined; + } + } +} + +function validateMethodOverride(proto, name) { + var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null; + + // Disallow overriding of base class methods unless explicitly allowed. + if (ReactClassMixin.hasOwnProperty(name)) { + !(specPolicy === SpecPolicy.OVERRIDE_BASE) ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name) : invariant(false) : undefined; + } + + // Disallow defining methods more than once unless explicitly allowed. + if (proto.hasOwnProperty(name)) { + !(specPolicy === SpecPolicy.DEFINE_MANY || specPolicy === SpecPolicy.DEFINE_MANY_MERGED) ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name) : invariant(false) : undefined; + } +} + +/** + * Mixin helper which handles policy validation and reserved + * specification keys when building React classses. + */ +function mixSpecIntoComponent(Constructor, spec) { + if (!spec) { + return; + } + + !(typeof spec !== 'function') ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to ' + 'use a component class as a mixin. Instead, just use a regular object.') : invariant(false) : undefined; + !!ReactElement.isValidElement(spec) ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.') : invariant(false) : undefined; + + var proto = Constructor.prototype; + + // By handling mixins before any other properties, we ensure the same + // chaining order is applied to methods with DEFINE_MANY policy, whether + // mixins are listed before or after these methods in the spec. + if (spec.hasOwnProperty(MIXINS_KEY)) { + RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); + } + + for (var name in spec) { + if (!spec.hasOwnProperty(name)) { + continue; + } + + if (name === MIXINS_KEY) { + // We have already handled mixins in a special case above. + continue; + } + + var property = spec[name]; + validateMethodOverride(proto, name); + + if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { + RESERVED_SPEC_KEYS[name](Constructor, property); + } else { + // Setup methods on prototype: + // The following member methods should not be automatically bound: + // 1. Expected ReactClass methods (in the "interface"). + // 2. Overridden methods (that were mixed in). + var isReactClassMethod = ReactClassInterface.hasOwnProperty(name); + var isAlreadyDefined = proto.hasOwnProperty(name); + var isFunction = typeof property === 'function'; + var shouldAutoBind = isFunction && !isReactClassMethod && !isAlreadyDefined && spec.autobind !== false; + + if (shouldAutoBind) { + if (!proto.__reactAutoBindMap) { + proto.__reactAutoBindMap = {}; + } + proto.__reactAutoBindMap[name] = property; + proto[name] = property; + } else { + if (isAlreadyDefined) { + var specPolicy = ReactClassInterface[name]; + + // These cases should already be caught by validateMethodOverride. + !(isReactClassMethod && (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY)) ? "development" !== 'production' ? invariant(false, 'ReactClass: Unexpected spec policy %s for key %s ' + 'when mixing in component specs.', specPolicy, name) : invariant(false) : undefined; + + // For methods which are defined more than once, call the existing + // methods before calling the new property, merging if appropriate. + if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) { + proto[name] = createMergedResultFunction(proto[name], property); + } else if (specPolicy === SpecPolicy.DEFINE_MANY) { + proto[name] = createChainedFunction(proto[name], property); + } + } else { + proto[name] = property; + if ("development" !== 'production') { + // Add verbose displayName to the function, which helps when looking + // at profiling tools. + if (typeof property === 'function' && spec.displayName) { + proto[name].displayName = spec.displayName + '_' + name; + } + } + } + } + } + } +} + +function mixStaticSpecIntoComponent(Constructor, statics) { + if (!statics) { + return; + } + for (var name in statics) { + var property = statics[name]; + if (!statics.hasOwnProperty(name)) { + continue; + } + + var isReserved = (name in RESERVED_SPEC_KEYS); + !!isReserved ? "development" !== 'production' ? invariant(false, 'ReactClass: You are attempting to define a reserved ' + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 'as an instance property instead; it will still be accessible on the ' + 'constructor.', name) : invariant(false) : undefined; + + var isInherited = (name in Constructor); + !!isInherited ? "development" !== 'production' ? invariant(false, 'ReactClass: You are attempting to define ' + '`%s` on your component more than once. This conflict may be ' + 'due to a mixin.', name) : invariant(false) : undefined; + Constructor[name] = property; + } +} + +/** + * Merge two objects, but throw if both contain the same key. + * + * @param {object} one The first object, which is mutated. + * @param {object} two The second object + * @return {object} one after it has been mutated to contain everything in two. + */ +function mergeIntoWithNoDuplicateKeys(one, two) { + !(one && two && typeof one === 'object' && typeof two === 'object') ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.') : invariant(false) : undefined; + + for (var key in two) { + if (two.hasOwnProperty(key)) { + !(one[key] === undefined) ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: `%s`. This conflict ' + 'may be due to a mixin; in particular, this may be caused by two ' + 'getInitialState() or getDefaultProps() methods returning objects ' + 'with clashing keys.', key) : invariant(false) : undefined; + one[key] = two[key]; + } + } + return one; +} + +/** + * Creates a function that invokes two functions and merges their return values. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createMergedResultFunction(one, two) { + return function mergedResult() { + var a = one.apply(this, arguments); + var b = two.apply(this, arguments); + if (a == null) { + return b; + } else if (b == null) { + return a; + } + var c = {}; + mergeIntoWithNoDuplicateKeys(c, a); + mergeIntoWithNoDuplicateKeys(c, b); + return c; + }; +} + +/** + * Creates a function that invokes two functions and ignores their return vales. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createChainedFunction(one, two) { + return function chainedFunction() { + one.apply(this, arguments); + two.apply(this, arguments); + }; +} + +/** + * Binds a method to the component. + * + * @param {object} component Component whose method is going to be bound. + * @param {function} method Method to be bound. + * @return {function} The bound method. + */ +function bindAutoBindMethod(component, method) { + var boundMethod = method.bind(component); + if ("development" !== 'production') { + boundMethod.__reactBoundContext = component; + boundMethod.__reactBoundMethod = method; + boundMethod.__reactBoundArguments = null; + var componentName = component.constructor.displayName; + var _bind = boundMethod.bind; + /* eslint-disable block-scoped-var, no-undef */ + boundMethod.bind = function (newThis) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + // User is trying to bind() an autobound method; we effectively will + // ignore the value of "this" that the user is trying to use, so + // let's warn. + if (newThis !== component && newThis !== null) { + "development" !== 'production' ? warning(false, 'bind(): React component methods may only be bound to the ' + 'component instance. See %s', componentName) : undefined; + } else if (!args.length) { + "development" !== 'production' ? warning(false, 'bind(): You are binding a component method to the component. ' + 'React does this for you automatically in a high-performance ' + 'way, so you can safely remove this call. See %s', componentName) : undefined; + return boundMethod; + } + var reboundMethod = _bind.apply(boundMethod, arguments); + reboundMethod.__reactBoundContext = component; + reboundMethod.__reactBoundMethod = method; + reboundMethod.__reactBoundArguments = args; + return reboundMethod; + /* eslint-enable */ + }; + } + return boundMethod; +} + +/** + * Binds all auto-bound methods in a component. + * + * @param {object} component Component whose method is going to be bound. + */ +function bindAutoBindMethods(component) { + for (var autoBindKey in component.__reactAutoBindMap) { + if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) { + var method = component.__reactAutoBindMap[autoBindKey]; + component[autoBindKey] = bindAutoBindMethod(component, method); + } + } +} + +/** + * Add more to the ReactClass base class. These are all legacy features and + * therefore not already part of the modern ReactComponent. + */ +var ReactClassMixin = { + + /** + * TODO: This will be deprecated because state should always keep a consistent + * type signature and the only use case for this, is to avoid that. + */ + replaceState: function (newState, callback) { + this.updater.enqueueReplaceState(this, newState); + if (callback) { + this.updater.enqueueCallback(this, callback); + } + }, + + /** + * Checks whether or not this composite component is mounted. + * @return {boolean} True if mounted, false otherwise. + * @protected + * @final + */ + isMounted: function () { + return this.updater.isMounted(this); + }, + + /** + * Sets a subset of the props. + * + * @param {object} partialProps Subset of the next props. + * @param {?function} callback Called after props are updated. + * @final + * @public + * @deprecated + */ + setProps: function (partialProps, callback) { + if ("development" !== 'production') { + warnSetProps(); + } + this.updater.enqueueSetProps(this, partialProps); + if (callback) { + this.updater.enqueueCallback(this, callback); + } + }, + + /** + * Replace all the props. + * + * @param {object} newProps Subset of the next props. + * @param {?function} callback Called after props are updated. + * @final + * @public + * @deprecated + */ + replaceProps: function (newProps, callback) { + if ("development" !== 'production') { + warnSetProps(); + } + this.updater.enqueueReplaceProps(this, newProps); + if (callback) { + this.updater.enqueueCallback(this, callback); + } + } +}; + +var ReactClassComponent = function () {}; +assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin); + +/** + * Module for creating composite components. + * + * @class ReactClass + */ +var ReactClass = { + + /** + * Creates a composite component class given a class specification. + * + * @param {object} spec Class specification (which must define `render`). + * @return {function} Component constructor function. + * @public + */ + createClass: function (spec) { + var Constructor = function (props, context, updater) { + // This constructor is overridden by mocks. The argument is used + // by mocks to assert on what gets mounted. + + if ("development" !== 'production') { + "development" !== 'production' ? warning(this instanceof Constructor, 'Something is calling a React component directly. Use a factory or ' + 'JSX instead. See: https://fb.me/react-legacyfactory') : undefined; + } + + // Wire up auto-binding + if (this.__reactAutoBindMap) { + bindAutoBindMethods(this); + } + + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + + this.state = null; + + // ReactClasses doesn't have constructors. Instead, they use the + // getInitialState and componentWillMount methods for initialization. + + var initialState = this.getInitialState ? this.getInitialState() : null; + if ("development" !== 'production') { + // We allow auto-mocks to proceed as if they're returning null. + if (typeof initialState === 'undefined' && this.getInitialState._isMockFunction) { + // This is probably bad practice. Consider warning here and + // deprecating this convenience. + initialState = null; + } + } + !(typeof initialState === 'object' && !Array.isArray(initialState)) ? "development" !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : invariant(false) : undefined; + + this.state = initialState; + }; + Constructor.prototype = new ReactClassComponent(); + Constructor.prototype.constructor = Constructor; + + injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); + + mixSpecIntoComponent(Constructor, spec); + + // Initialize the defaultProps property after all mixins have been merged. + if (Constructor.getDefaultProps) { + Constructor.defaultProps = Constructor.getDefaultProps(); + } + + if ("development" !== 'production') { + // This is a tag to indicate that the use of these method names is ok, + // since it's used with createClass. If it's not, then it's likely a + // mistake so we'll warn you to use the static property, property + // initializer or constructor respectively. + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps.isReactClassApproved = {}; + } + if (Constructor.prototype.getInitialState) { + Constructor.prototype.getInitialState.isReactClassApproved = {}; + } + } + + !Constructor.prototype.render ? "development" !== 'production' ? invariant(false, 'createClass(...): Class specification must implement a `render` method.') : invariant(false) : undefined; + + if ("development" !== 'production') { + "development" !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : undefined; + "development" !== 'production' ? warning(!Constructor.prototype.componentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component') : undefined; + } + + // Reduce time spent doing lookups by setting these on the prototype. + for (var methodName in ReactClassInterface) { + if (!Constructor.prototype[methodName]) { + Constructor.prototype[methodName] = null; + } + } + + return Constructor; + }, + + injection: { + injectMixin: function (mixin) { + injectedMixins.push(mixin); + } + } + +}; + +module.exports = ReactClass; +},{"154":154,"161":161,"165":165,"166":166,"173":173,"24":24,"34":34,"57":57,"76":76,"80":80,"81":81}],34:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponent + */ + +'use strict'; + +var ReactNoopUpdateQueue = _dereq_(76); + +var canDefineProperty = _dereq_(117); +var emptyObject = _dereq_(154); +var invariant = _dereq_(161); +var warning = _dereq_(173); + +/** + * Base class helpers for the updating state of a component. + */ +function ReactComponent(props, context, updater) { + this.props = props; + this.context = context; + this.refs = emptyObject; + // We initialize the default updater but the real one gets injected by the + // renderer. + this.updater = updater || ReactNoopUpdateQueue; +} + +ReactComponent.prototype.isReactComponent = {}; + +/** + * Sets a subset of the state. Always use this to mutate + * state. You should treat `this.state` as immutable. + * + * There is no guarantee that `this.state` will be immediately updated, so + * accessing `this.state` after calling this method may return the old value. + * + * There is no guarantee that calls to `setState` will run synchronously, + * as they may eventually be batched together. You can provide an optional + * callback that will be executed when the call to setState is actually + * completed. + * + * When a function is provided to setState, it will be called at some point in + * the future (not synchronously). It will be called with the up to date + * component arguments (state, props, context). These values can be different + * from this.* because your function may be called after receiveProps but before + * shouldComponentUpdate, and this new state, props, and context will not yet be + * assigned to this. + * + * @param {object|function} partialState Next partial state or function to + * produce next partial state to be merged with current state. + * @param {?function} callback Called after state is updated. + * @final + * @protected + */ +ReactComponent.prototype.setState = function (partialState, callback) { + !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.') : invariant(false) : undefined; + if ("development" !== 'production') { + "development" !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : undefined; + } + this.updater.enqueueSetState(this, partialState); + if (callback) { + this.updater.enqueueCallback(this, callback); + } +}; + +/** + * Forces an update. This should only be invoked when it is known with + * certainty that we are **not** in a DOM transaction. + * + * You may want to call this when you know that some deeper aspect of the + * component's state has changed but `setState` was not called. + * + * This will not invoke `shouldComponentUpdate`, but it will invoke + * `componentWillUpdate` and `componentDidUpdate`. + * + * @param {?function} callback Called after update is complete. + * @final + * @protected + */ +ReactComponent.prototype.forceUpdate = function (callback) { + this.updater.enqueueForceUpdate(this); + if (callback) { + this.updater.enqueueCallback(this, callback); + } +}; + +/** + * Deprecated APIs. These APIs used to exist on classic React classes but since + * we would like to deprecate them, we're not going to move them over to this + * modern base class. Instead, we define a getter that warns if it's accessed. + */ +if ("development" !== 'production') { + var deprecatedAPIs = { + getDOMNode: ['getDOMNode', 'Use ReactDOM.findDOMNode(component) instead.'], + isMounted: ['isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.'], + replaceProps: ['replaceProps', 'Instead, call render again at the top level.'], + replaceState: ['replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).'], + setProps: ['setProps', 'Instead, call render again at the top level.'] + }; + var defineDeprecationWarning = function (methodName, info) { + if (canDefineProperty) { + Object.defineProperty(ReactComponent.prototype, methodName, { + get: function () { + "development" !== 'production' ? warning(false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1]) : undefined; + return undefined; + } + }); + } + }; + for (var fnName in deprecatedAPIs) { + if (deprecatedAPIs.hasOwnProperty(fnName)) { + defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); + } + } +} + +module.exports = ReactComponent; +},{"117":117,"154":154,"161":161,"173":173,"76":76}],35:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentBrowserEnvironment + */ + +'use strict'; + +var ReactDOMIDOperations = _dereq_(45); +var ReactMount = _dereq_(72); + +/** + * Abstracts away all functionality of the reconciler that requires knowledge of + * the browser context. TODO: These callers should be refactored to avoid the + * need for this injection. + */ +var ReactComponentBrowserEnvironment = { + + processChildrenUpdates: ReactDOMIDOperations.dangerouslyProcessChildrenUpdates, + + replaceNodeWithMarkupByID: ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + + /** + * If a particular environment requires that some resources be cleaned up, + * specify this in the injected Mixin. In the DOM, we would likely want to + * purge any cached node ID lookups. + * + * @private + */ + unmountIDFromEnvironment: function (rootNodeID) { + ReactMount.purgeID(rootNodeID); + } + +}; + +module.exports = ReactComponentBrowserEnvironment; +},{"45":45,"72":72}],36:[function(_dereq_,module,exports){ +/** + * Copyright 2014-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentEnvironment + */ + +'use strict'; + +var invariant = _dereq_(161); + +var injected = false; + +var ReactComponentEnvironment = { + + /** + * Optionally injectable environment dependent cleanup hook. (server vs. + * browser etc). Example: A browser system caches DOM nodes based on component + * ID and must remove that cache entry when this instance is unmounted. + */ + unmountIDFromEnvironment: null, + + /** + * Optionally injectable hook for swapping out mount images in the middle of + * the tree. + */ + replaceNodeWithMarkupByID: null, + + /** + * Optionally injectable hook for processing a queue of child updates. Will + * later move into MultiChildComponents. + */ + processChildrenUpdates: null, + + injection: { + injectEnvironment: function (environment) { + !!injected ? "development" !== 'production' ? invariant(false, 'ReactCompositeComponent: injectEnvironment() can only be called once.') : invariant(false) : undefined; + ReactComponentEnvironment.unmountIDFromEnvironment = environment.unmountIDFromEnvironment; + ReactComponentEnvironment.replaceNodeWithMarkupByID = environment.replaceNodeWithMarkupByID; + ReactComponentEnvironment.processChildrenUpdates = environment.processChildrenUpdates; + injected = true; + } + } + +}; + +module.exports = ReactComponentEnvironment; +},{"161":161}],37:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentWithPureRenderMixin + */ + +'use strict'; + +var shallowCompare = _dereq_(140); + +/** + * If your React component's render function is "pure", e.g. it will render the + * same result given the same props and state, provide this Mixin for a + * considerable performance boost. + * + * Most React components have pure render functions. + * + * Example: + * + * var ReactComponentWithPureRenderMixin = + * require('ReactComponentWithPureRenderMixin'); + * React.createClass({ + * mixins: [ReactComponentWithPureRenderMixin], + * + * render: function() { + * return
foo
; + * } + * }); + * + * Note: This only checks shallow equality for props and state. If these contain + * complex data structures this mixin may have false-negatives for deeper + * differences. Only mixin to components which have simple props and state, or + * use `forceUpdate()` when you know deep data structures have changed. + */ +var ReactComponentWithPureRenderMixin = { + shouldComponentUpdate: function (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } +}; + +module.exports = ReactComponentWithPureRenderMixin; +},{"140":140}],38:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCompositeComponent + */ + +'use strict'; + +var ReactComponentEnvironment = _dereq_(36); +var ReactCurrentOwner = _dereq_(39); +var ReactElement = _dereq_(57); +var ReactInstanceMap = _dereq_(68); +var ReactPerf = _dereq_(78); +var ReactPropTypeLocations = _dereq_(81); +var ReactPropTypeLocationNames = _dereq_(80); +var ReactReconciler = _dereq_(84); +var ReactUpdateQueue = _dereq_(95); + +var assign = _dereq_(24); +var emptyObject = _dereq_(154); +var invariant = _dereq_(161); +var shouldUpdateReactComponent = _dereq_(141); +var warning = _dereq_(173); + +function getDeclarationErrorAddendum(component) { + var owner = component._currentElement._owner || null; + if (owner) { + var name = owner.getName(); + if (name) { + return ' Check the render method of `' + name + '`.'; + } + } + return ''; +} + +function StatelessComponent(Component) {} +StatelessComponent.prototype.render = function () { + var Component = ReactInstanceMap.get(this)._currentElement.type; + return Component(this.props, this.context, this.updater); +}; + +/** + * ------------------ The Life-Cycle of a Composite Component ------------------ + * + * - constructor: Initialization of state. The instance is now retained. + * - componentWillMount + * - render + * - [children's constructors] + * - [children's componentWillMount and render] + * - [children's componentDidMount] + * - componentDidMount + * + * Update Phases: + * - componentWillReceiveProps (only called if parent updated) + * - shouldComponentUpdate + * - componentWillUpdate + * - render + * - [children's constructors or receive props phases] + * - componentDidUpdate + * + * - componentWillUnmount + * - [children's componentWillUnmount] + * - [children destroyed] + * - (destroyed): The instance is now blank, released by React and ready for GC. + * + * ----------------------------------------------------------------------------- + */ + +/** + * An incrementing ID assigned to each component when it is mounted. This is + * used to enforce the order in which `ReactUpdates` updates dirty components. + * + * @private + */ +var nextMountID = 1; + +/** + * @lends {ReactCompositeComponent.prototype} + */ +var ReactCompositeComponentMixin = { + + /** + * Base constructor for all composite component. + * + * @param {ReactElement} element + * @final + * @internal + */ + construct: function (element) { + this._currentElement = element; + this._rootNodeID = null; + this._instance = null; + + // See ReactUpdateQueue + this._pendingElement = null; + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + + this._renderedComponent = null; + + this._context = null; + this._mountOrder = 0; + this._topLevelWrapper = null; + + // See ReactUpdates and ReactUpdateQueue. + this._pendingCallbacks = null; + }, + + /** + * Initializes the component, renders markup, and registers event listeners. + * + * @param {string} rootID DOM ID of the root node. + * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + * @return {?string} Rendered markup to be inserted into the DOM. + * @final + * @internal + */ + mountComponent: function (rootID, transaction, context) { + this._context = context; + this._mountOrder = nextMountID++; + this._rootNodeID = rootID; + + var publicProps = this._processProps(this._currentElement.props); + var publicContext = this._processContext(context); + + var Component = this._currentElement.type; + + // Initialize the public class + var inst; + var renderedElement; + + // This is a way to detect if Component is a stateless arrow function + // component, which is not newable. It might not be 100% reliable but is + // something we can do until we start detecting that Component extends + // React.Component. We already assume that typeof Component === 'function'. + var canInstantiate = ('prototype' in Component); + + if (canInstantiate) { + if ("development" !== 'production') { + ReactCurrentOwner.current = this; + try { + inst = new Component(publicProps, publicContext, ReactUpdateQueue); + } finally { + ReactCurrentOwner.current = null; + } + } else { + inst = new Component(publicProps, publicContext, ReactUpdateQueue); + } + } + + if (!canInstantiate || inst === null || inst === false || ReactElement.isValidElement(inst)) { + renderedElement = inst; + inst = new StatelessComponent(Component); + } + + if ("development" !== 'production') { + // This will throw later in _renderValidatedComponent, but add an early + // warning now to help debugging + if (inst.render == null) { + "development" !== 'production' ? warning(false, '%s(...): No `render` method found on the returned component ' + 'instance: you may have forgotten to define `render`, returned ' + 'null/false from a stateless component, or tried to render an ' + 'element whose type is a function that isn\'t a React component.', Component.displayName || Component.name || 'Component') : undefined; + } else { + // We support ES6 inheriting from React.Component, the module pattern, + // and stateless components, but not ES6 classes that don't extend + "development" !== 'production' ? warning(Component.prototype && Component.prototype.isReactComponent || !canInstantiate || !(inst instanceof Component), '%s(...): React component classes must extend React.Component.', Component.displayName || Component.name || 'Component') : undefined; + } + } + + // These should be set up in the constructor, but as a convenience for + // simpler class abstractions, we set them up after the fact. + inst.props = publicProps; + inst.context = publicContext; + inst.refs = emptyObject; + inst.updater = ReactUpdateQueue; + + this._instance = inst; + + // Store a reference from the instance back to the internal representation + ReactInstanceMap.set(inst, this); + + if ("development" !== 'production') { + // Since plain JS classes are defined without any special initialization + // logic, we can not catch common errors early. Therefore, we have to + // catch them here, at initialization time, instead. + "development" !== 'production' ? warning(!inst.getInitialState || inst.getInitialState.isReactClassApproved, 'getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', this.getName() || 'a component') : undefined; + "development" !== 'production' ? warning(!inst.getDefaultProps || inst.getDefaultProps.isReactClassApproved, 'getDefaultProps was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.', this.getName() || 'a component') : undefined; + "development" !== 'production' ? warning(!inst.propTypes, 'propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', this.getName() || 'a component') : undefined; + "development" !== 'production' ? warning(!inst.contextTypes, 'contextTypes was defined as an instance property on %s. Use a ' + 'static property to define contextTypes instead.', this.getName() || 'a component') : undefined; + "development" !== 'production' ? warning(typeof inst.componentShouldUpdate !== 'function', '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', this.getName() || 'A component') : undefined; + "development" !== 'production' ? warning(typeof inst.componentDidUnmount !== 'function', '%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', this.getName() || 'A component') : undefined; + "development" !== 'production' ? warning(typeof inst.componentWillRecieveProps !== 'function', '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', this.getName() || 'A component') : undefined; + } + + var initialState = inst.state; + if (initialState === undefined) { + inst.state = initialState = null; + } + !(typeof initialState === 'object' && !Array.isArray(initialState)) ? "development" !== 'production' ? invariant(false, '%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : invariant(false) : undefined; + + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + + if (inst.componentWillMount) { + inst.componentWillMount(); + // When mounting, calls to `setState` by `componentWillMount` will set + // `this._pendingStateQueue` without triggering a re-render. + if (this._pendingStateQueue) { + inst.state = this._processPendingState(inst.props, inst.context); + } + } + + // If not a stateless component, we now render + if (renderedElement === undefined) { + renderedElement = this._renderValidatedComponent(); + } + + this._renderedComponent = this._instantiateReactComponent(renderedElement); + + var markup = ReactReconciler.mountComponent(this._renderedComponent, rootID, transaction, this._processChildContext(context)); + if (inst.componentDidMount) { + transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); + } + + return markup; + }, + + /** + * Releases any resources allocated by `mountComponent`. + * + * @final + * @internal + */ + unmountComponent: function () { + var inst = this._instance; + + if (inst.componentWillUnmount) { + inst.componentWillUnmount(); + } + + ReactReconciler.unmountComponent(this._renderedComponent); + this._renderedComponent = null; + this._instance = null; + + // Reset pending fields + // Even if this component is scheduled for another update in ReactUpdates, + // it would still be ignored because these fields are reset. + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + this._pendingCallbacks = null; + this._pendingElement = null; + + // These fields do not really need to be reset since this object is no + // longer accessible. + this._context = null; + this._rootNodeID = null; + this._topLevelWrapper = null; + + // Delete the reference from the instance to this internal representation + // which allow the internals to be properly cleaned up even if the user + // leaks a reference to the public instance. + ReactInstanceMap.remove(inst); + + // Some existing components rely on inst.props even after they've been + // destroyed (in event handlers). + // TODO: inst.props = null; + // TODO: inst.state = null; + // TODO: inst.context = null; + }, + + /** + * Filters the context object to only contain keys specified in + * `contextTypes` + * + * @param {object} context + * @return {?object} + * @private + */ + _maskContext: function (context) { + var maskedContext = null; + var Component = this._currentElement.type; + var contextTypes = Component.contextTypes; + if (!contextTypes) { + return emptyObject; + } + maskedContext = {}; + for (var contextName in contextTypes) { + maskedContext[contextName] = context[contextName]; + } + return maskedContext; + }, + + /** + * Filters the context object to only contain keys specified in + * `contextTypes`, and asserts that they are valid. + * + * @param {object} context + * @return {?object} + * @private + */ + _processContext: function (context) { + var maskedContext = this._maskContext(context); + if ("development" !== 'production') { + var Component = this._currentElement.type; + if (Component.contextTypes) { + this._checkPropTypes(Component.contextTypes, maskedContext, ReactPropTypeLocations.context); + } + } + return maskedContext; + }, + + /** + * @param {object} currentContext + * @return {object} + * @private + */ + _processChildContext: function (currentContext) { + var Component = this._currentElement.type; + var inst = this._instance; + var childContext = inst.getChildContext && inst.getChildContext(); + if (childContext) { + !(typeof Component.childContextTypes === 'object') ? "development" !== 'production' ? invariant(false, '%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', this.getName() || 'ReactCompositeComponent') : invariant(false) : undefined; + if ("development" !== 'production') { + this._checkPropTypes(Component.childContextTypes, childContext, ReactPropTypeLocations.childContext); + } + for (var name in childContext) { + !(name in Component.childContextTypes) ? "development" !== 'production' ? invariant(false, '%s.getChildContext(): key "%s" is not defined in childContextTypes.', this.getName() || 'ReactCompositeComponent', name) : invariant(false) : undefined; + } + return assign({}, currentContext, childContext); + } + return currentContext; + }, + + /** + * Processes props by setting default values for unspecified props and + * asserting that the props are valid. Does not mutate its argument; returns + * a new props object with defaults merged in. + * + * @param {object} newProps + * @return {object} + * @private + */ + _processProps: function (newProps) { + if ("development" !== 'production') { + var Component = this._currentElement.type; + if (Component.propTypes) { + this._checkPropTypes(Component.propTypes, newProps, ReactPropTypeLocations.prop); + } + } + return newProps; + }, + + /** + * Assert that the props are valid + * + * @param {object} propTypes Map of prop name to a ReactPropType + * @param {object} props + * @param {string} location e.g. "prop", "context", "child context" + * @private + */ + _checkPropTypes: function (propTypes, props, location) { + // TODO: Stop validating prop types here and only use the element + // validation. + var componentName = this.getName(); + for (var propName in propTypes) { + if (propTypes.hasOwnProperty(propName)) { + var error; + try { + // This is intentionally an invariant that gets caught. It's the same + // behavior as without this statement except with a better message. + !(typeof propTypes[propName] === 'function') ? "development" !== 'production' ? invariant(false, '%s: %s type `%s` is invalid; it must be a function, usually ' + 'from React.PropTypes.', componentName || 'React class', ReactPropTypeLocationNames[location], propName) : invariant(false) : undefined; + error = propTypes[propName](props, propName, componentName, location); + } catch (ex) { + error = ex; + } + if (error instanceof Error) { + // We may want to extend this logic for similar errors in + // top-level render calls, so I'm abstracting it away into + // a function to minimize refactoring in the future + var addendum = getDeclarationErrorAddendum(this); + + if (location === ReactPropTypeLocations.prop) { + // Preface gives us something to blacklist in warning module + "development" !== 'production' ? warning(false, 'Failed Composite propType: %s%s', error.message, addendum) : undefined; + } else { + "development" !== 'production' ? warning(false, 'Failed Context Types: %s%s', error.message, addendum) : undefined; + } + } + } + } + }, + + receiveComponent: function (nextElement, transaction, nextContext) { + var prevElement = this._currentElement; + var prevContext = this._context; + + this._pendingElement = null; + + this.updateComponent(transaction, prevElement, nextElement, prevContext, nextContext); + }, + + /** + * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate` + * is set, update the component. + * + * @param {ReactReconcileTransaction} transaction + * @internal + */ + performUpdateIfNecessary: function (transaction) { + if (this._pendingElement != null) { + ReactReconciler.receiveComponent(this, this._pendingElement || this._currentElement, transaction, this._context); + } + + if (this._pendingStateQueue !== null || this._pendingForceUpdate) { + this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); + } + }, + + /** + * Perform an update to a mounted component. The componentWillReceiveProps and + * shouldComponentUpdate methods are called, then (assuming the update isn't + * skipped) the remaining update lifecycle methods are called and the DOM + * representation is updated. + * + * By default, this implements React's rendering and reconciliation algorithm. + * Sophisticated clients may wish to override this. + * + * @param {ReactReconcileTransaction} transaction + * @param {ReactElement} prevParentElement + * @param {ReactElement} nextParentElement + * @internal + * @overridable + */ + updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) { + var inst = this._instance; + + var nextContext = this._context === nextUnmaskedContext ? inst.context : this._processContext(nextUnmaskedContext); + var nextProps; + + // Distinguish between a props update versus a simple state update + if (prevParentElement === nextParentElement) { + // Skip checking prop types again -- we don't read inst.props to avoid + // warning for DOM component props in this upgrade + nextProps = nextParentElement.props; + } else { + nextProps = this._processProps(nextParentElement.props); + // An update here will schedule an update but immediately set + // _pendingStateQueue which will ensure that any state updates gets + // immediately reconciled instead of waiting for the next batch. + + if (inst.componentWillReceiveProps) { + inst.componentWillReceiveProps(nextProps, nextContext); + } + } + + var nextState = this._processPendingState(nextProps, nextContext); + + var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext); + + if ("development" !== 'production') { + "development" !== 'production' ? warning(typeof shouldUpdate !== 'undefined', '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : undefined; + } + + if (shouldUpdate) { + this._pendingForceUpdate = false; + // Will set `this.props`, `this.state` and `this.context`. + this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext); + } else { + // If it's determined that a component should not update, we still want + // to set props and state but we shortcut the rest of the update. + this._currentElement = nextParentElement; + this._context = nextUnmaskedContext; + inst.props = nextProps; + inst.state = nextState; + inst.context = nextContext; + } + }, + + _processPendingState: function (props, context) { + var inst = this._instance; + var queue = this._pendingStateQueue; + var replace = this._pendingReplaceState; + this._pendingReplaceState = false; + this._pendingStateQueue = null; + + if (!queue) { + return inst.state; + } + + if (replace && queue.length === 1) { + return queue[0]; + } + + var nextState = assign({}, replace ? queue[0] : inst.state); + for (var i = replace ? 1 : 0; i < queue.length; i++) { + var partial = queue[i]; + assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial); + } + + return nextState; + }, + + /** + * Merges new props and state, notifies delegate methods of update and + * performs update. + * + * @param {ReactElement} nextElement Next element + * @param {object} nextProps Next public object to set as properties. + * @param {?object} nextState Next object to set as state. + * @param {?object} nextContext Next public object to set as context. + * @param {ReactReconcileTransaction} transaction + * @param {?object} unmaskedContext + * @private + */ + _performComponentUpdate: function (nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) { + var inst = this._instance; + + var hasComponentDidUpdate = Boolean(inst.componentDidUpdate); + var prevProps; + var prevState; + var prevContext; + if (hasComponentDidUpdate) { + prevProps = inst.props; + prevState = inst.state; + prevContext = inst.context; + } + + if (inst.componentWillUpdate) { + inst.componentWillUpdate(nextProps, nextState, nextContext); + } + + this._currentElement = nextElement; + this._context = unmaskedContext; + inst.props = nextProps; + inst.state = nextState; + inst.context = nextContext; + + this._updateRenderedComponent(transaction, unmaskedContext); + + if (hasComponentDidUpdate) { + transaction.getReactMountReady().enqueue(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst); + } + }, + + /** + * Call the component's `render` method and update the DOM accordingly. + * + * @param {ReactReconcileTransaction} transaction + * @internal + */ + _updateRenderedComponent: function (transaction, context) { + var prevComponentInstance = this._renderedComponent; + var prevRenderedElement = prevComponentInstance._currentElement; + var nextRenderedElement = this._renderValidatedComponent(); + if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { + ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context)); + } else { + // These two IDs are actually the same! But nothing should rely on that. + var thisID = this._rootNodeID; + var prevComponentID = prevComponentInstance._rootNodeID; + ReactReconciler.unmountComponent(prevComponentInstance); + + this._renderedComponent = this._instantiateReactComponent(nextRenderedElement); + var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent, thisID, transaction, this._processChildContext(context)); + this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup); + } + }, + + /** + * @protected + */ + _replaceNodeWithMarkupByID: function (prevComponentID, nextMarkup) { + ReactComponentEnvironment.replaceNodeWithMarkupByID(prevComponentID, nextMarkup); + }, + + /** + * @protected + */ + _renderValidatedComponentWithoutOwnerOrContext: function () { + var inst = this._instance; + var renderedComponent = inst.render(); + if ("development" !== 'production') { + // We allow auto-mocks to proceed as if they're returning null. + if (typeof renderedComponent === 'undefined' && inst.render._isMockFunction) { + // This is probably bad practice. Consider warning here and + // deprecating this convenience. + renderedComponent = null; + } + } + + return renderedComponent; + }, + + /** + * @private + */ + _renderValidatedComponent: function () { + var renderedComponent; + ReactCurrentOwner.current = this; + try { + renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext(); + } finally { + ReactCurrentOwner.current = null; + } + !( + // TODO: An `isValidNode` function would probably be more appropriate + renderedComponent === null || renderedComponent === false || ReactElement.isValidElement(renderedComponent)) ? "development" !== 'production' ? invariant(false, '%s.render(): A valid ReactComponent must be returned. You may have ' + 'returned undefined, an array or some other invalid object.', this.getName() || 'ReactCompositeComponent') : invariant(false) : undefined; + return renderedComponent; + }, + + /** + * Lazily allocates the refs object and stores `component` as `ref`. + * + * @param {string} ref Reference name. + * @param {component} component Component to store as `ref`. + * @final + * @private + */ + attachRef: function (ref, component) { + var inst = this.getPublicInstance(); + !(inst != null) ? "development" !== 'production' ? invariant(false, 'Stateless function components cannot have refs.') : invariant(false) : undefined; + var publicComponentInstance = component.getPublicInstance(); + if ("development" !== 'production') { + var componentName = component && component.getName ? component.getName() : 'a component'; + "development" !== 'production' ? warning(publicComponentInstance != null, 'Stateless function components cannot be given refs ' + '(See ref "%s" in %s created by %s). ' + 'Attempts to access this ref will fail.', ref, componentName, this.getName()) : undefined; + } + var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs; + refs[ref] = publicComponentInstance; + }, + + /** + * Detaches a reference name. + * + * @param {string} ref Name to dereference. + * @final + * @private + */ + detachRef: function (ref) { + var refs = this.getPublicInstance().refs; + delete refs[ref]; + }, + + /** + * Get a text description of the component that can be used to identify it + * in error messages. + * @return {string} The name or null. + * @internal + */ + getName: function () { + var type = this._currentElement.type; + var constructor = this._instance && this._instance.constructor; + return type.displayName || constructor && constructor.displayName || type.name || constructor && constructor.name || null; + }, + + /** + * Get the publicly accessible representation of this component - i.e. what + * is exposed by refs and returned by render. Can be null for stateless + * components. + * + * @return {ReactComponent} the public component instance. + * @internal + */ + getPublicInstance: function () { + var inst = this._instance; + if (inst instanceof StatelessComponent) { + return null; + } + return inst; + }, + + // Stub + _instantiateReactComponent: null + +}; + +ReactPerf.measureMethods(ReactCompositeComponentMixin, 'ReactCompositeComponent', { + mountComponent: 'mountComponent', + updateComponent: 'updateComponent', + _renderValidatedComponent: '_renderValidatedComponent' +}); + +var ReactCompositeComponent = { + + Mixin: ReactCompositeComponentMixin + +}; + +module.exports = ReactCompositeComponent; +},{"141":141,"154":154,"161":161,"173":173,"24":24,"36":36,"39":39,"57":57,"68":68,"78":78,"80":80,"81":81,"84":84,"95":95}],39:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCurrentOwner + */ + +'use strict'; + +/** + * Keeps track of the current owner. + * + * The current owner is the component who should own any components that are + * currently being constructed. + */ +var ReactCurrentOwner = { + + /** + * @internal + * @type {ReactComponent} + */ + current: null + +}; + +module.exports = ReactCurrentOwner; +},{}],40:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOM + */ + +/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__*/ + +'use strict'; + +var ReactCurrentOwner = _dereq_(39); +var ReactDOMTextComponent = _dereq_(51); +var ReactDefaultInjection = _dereq_(54); +var ReactInstanceHandles = _dereq_(67); +var ReactMount = _dereq_(72); +var ReactPerf = _dereq_(78); +var ReactReconciler = _dereq_(84); +var ReactUpdates = _dereq_(96); +var ReactVersion = _dereq_(97); + +var findDOMNode = _dereq_(122); +var renderSubtreeIntoContainer = _dereq_(137); +var warning = _dereq_(173); + +ReactDefaultInjection.inject(); + +var render = ReactPerf.measure('React', 'render', ReactMount.render); + +var React = { + findDOMNode: findDOMNode, + render: render, + unmountComponentAtNode: ReactMount.unmountComponentAtNode, + version: ReactVersion, + + /* eslint-disable camelcase */ + unstable_batchedUpdates: ReactUpdates.batchedUpdates, + unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer +}; + +// Inject the runtime into a devtools global hook regardless of browser. +// Allows for debugging when the hook is injected on the page. +/* eslint-enable camelcase */ +if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ + CurrentOwner: ReactCurrentOwner, + InstanceHandles: ReactInstanceHandles, + Mount: ReactMount, + Reconciler: ReactReconciler, + TextComponent: ReactDOMTextComponent + }); +} + +if ("development" !== 'production') { + var ExecutionEnvironment = _dereq_(147); + if (ExecutionEnvironment.canUseDOM && window.top === window.self) { + + // First check if devtools is not installed + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { + // If we're in Chrome or Firefox, provide a download link if not installed. + if (navigator.userAgent.indexOf('Chrome') > -1 && navigator.userAgent.indexOf('Edge') === -1 || navigator.userAgent.indexOf('Firefox') > -1) { + console.debug('Download the React DevTools for a better development experience: ' + 'https://fb.me/react-devtools'); + } + } + + // If we're in IE8, check to see if we are in compatibility mode and provide + // information on preventing compatibility mode + var ieCompatibilityMode = document.documentMode && document.documentMode < 8; + + "development" !== 'production' ? warning(!ieCompatibilityMode, 'Internet Explorer is running in compatibility mode; please add the ' + 'following tag to your HTML to prevent this from happening: ' + '') : undefined; + + var expectedFeatures = [ + // shims + Array.isArray, Array.prototype.every, Array.prototype.forEach, Array.prototype.indexOf, Array.prototype.map, Date.now, Function.prototype.bind, Object.keys, String.prototype.split, String.prototype.trim, + + // shams + Object.create, Object.freeze]; + + for (var i = 0; i < expectedFeatures.length; i++) { + if (!expectedFeatures[i]) { + console.error('One or more ES5 shim/shams expected by React are not available: ' + 'https://fb.me/react-warning-polyfills'); + break; + } + } + } +} + +module.exports = React; +},{"122":122,"137":137,"147":147,"173":173,"39":39,"51":51,"54":54,"67":67,"72":72,"78":78,"84":84,"96":96,"97":97}],41:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMButton + */ + +'use strict'; + +var mouseListenerNames = { + onClick: true, + onDoubleClick: true, + onMouseDown: true, + onMouseMove: true, + onMouseUp: true, + + onClickCapture: true, + onDoubleClickCapture: true, + onMouseDownCapture: true, + onMouseMoveCapture: true, + onMouseUpCapture: true +}; + +/** + * Implements a