From 035144282a980252881fb8202a7d9f478317673f Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Mon, 12 Sep 2016 11:49:24 +0300 Subject: [PATCH 1/8] Bug 1301772 - Turn on Insecure Password Warning in Firefox Beta. r=florian MozReview-Commit-ID: 4IiyTMeIFan --- browser/app/profile/firefox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index d3cddd41aaef..070ddd023d5d 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1220,8 +1220,8 @@ pref("social.shareDirectory", "https://activations.cdn.mozilla.net/sharePanel.ht pref("security.mixed_content.block_active_content", true); // Show degraded UI for http pages with password fields. -// Only for Nightly and Dev Edition for not, not for beta or release. -#ifndef RELEASE_BUILD +// Only for Nightly, Dev Edition and early beta, not for late beta or release. +#ifdef EARLY_BETA_OR_EARLIER pref("security.insecure_password.ui.enabled", true); #else pref("security.insecure_password.ui.enabled", false); From faa5281786081da5331eae9be72c149ef0c8b29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Mon, 12 Sep 2016 13:32:25 +0200 Subject: [PATCH 2/8] Bug 1299437 - Manually calculate height changes of the control center's permission list until we have a real fix for sizing issues in panelmultiview, r=jaws. --- browser/base/content/browser.js | 19 +++++++++++++++++-- .../controlcenter/content/panel.inc.xul | 4 ++-- .../customizableui/content/panelUI.xml | 14 ++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 0e1a634cc7a8..3d181ac5110d 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -6852,7 +6852,7 @@ var gIdentityHandler = { this._sharingState = tab._sharingState; if (this._identityPopup.state == "open") { - this.updateSitePermissions(); + this._handleHeightChange(() => this.updateSitePermissions()); } }, @@ -7364,6 +7364,20 @@ var gIdentityHandler = { this.updatePermissionHint(); }, + _handleHeightChange: function(aFunction, aWillShowReloadHint) { + let heightBefore = getComputedStyle(this._permissionList).height; + aFunction(); + let heightAfter = getComputedStyle(this._permissionList).height; + // Showing the reload hint increases the height, we need to account for it. + if (aWillShowReloadHint) { + heightAfter = parseInt(heightAfter) + + parseInt(getComputedStyle(this._permissionList.nextSibling).height); + } + let heightChange = parseInt(heightAfter) - parseInt(heightBefore); + if (heightChange) + this._identityPopupMultiView.setHeightToFit(heightChange); + }, + _createPermissionItem: function (aPermission) { let container = document.createElement("hbox"); container.setAttribute("class", "identity-popup-permission-item"); @@ -7393,7 +7407,8 @@ var gIdentityHandler = { let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip"); button.setAttribute("tooltiptext", tooltiptext); button.addEventListener("command", () => { - this._permissionList.removeChild(container); + this._handleHeightChange(() => + this._permissionList.removeChild(container), !this._permissionJustRemoved); if (aPermission.inUse && ["camera", "microphone", "screen"].includes(aPermission.id)) { let windowId = this._sharingState.windowId; diff --git a/browser/components/controlcenter/content/panel.inc.xul b/browser/components/controlcenter/content/panel.inc.xul index 5e7adcd60487..2aa618d580f3 100644 --- a/browser/components/controlcenter/content/panel.inc.xul +++ b/browser/components/controlcenter/content/panel.inc.xul @@ -16,7 +16,7 @@ - + @@ -91,8 +91,8 @@ class="identity-popup-headline" value="&identity.permissions;"/> - &identity.permissionsEmpty; &identity.permissionsReloadHint; + &identity.permissionsEmpty; diff --git a/browser/components/customizableui/content/panelUI.xml b/browser/components/customizableui/content/panelUI.xml index 957c037e3fb2..fdd7a8d83591 100644 --- a/browser/components/customizableui/content/panelUI.xml +++ b/browser/components/customizableui/content/panelUI.xml @@ -338,7 +338,7 @@ break; case "popuphidden": this.removeAttribute("panelopen"); - this._mainView.style.removeProperty("max-height"); + this._mainView.style.removeProperty("height"); this.showMainView(); this._mainViewObserver.disconnect(); break; @@ -366,7 +366,7 @@ // Ignore the mutation that'll fire when we set the height of // the main view. this.ignoreMutations = true; - this._mainView.style.maxHeight = + this._mainView.style.height = this.getBoundingClientRect().height + "px"; this.ignoreMutations = false; ]]> @@ -416,8 +416,11 @@ + to be the same as the view's height. + If the caller can give a hint of the expected height change with the + optional aExpectedChange parameter, it prevents flicker. --> + { if (height != getComputedStyle(this).height || --count == 0) { clearInterval(interval); From bbdcbf68bcd5049275a7320172e9a415ea88596a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Mon, 12 Sep 2016 15:20:25 +0200 Subject: [PATCH 3/8] Bug 1299437 - follow-up to fix browser_permissions.js bustage, r=bustage-fix. --- browser/base/content/test/general/browser_permissions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/base/content/test/general/browser_permissions.js b/browser/base/content/test/general/browser_permissions.js index 2d338afce770..a6a46880c7a6 100644 --- a/browser/base/content/test/general/browser_permissions.js +++ b/browser/base/content/test/general/browser_permissions.js @@ -37,7 +37,7 @@ add_task(function* testMainViewVisible() { yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); let permissionsList = document.getElementById("identity-popup-permission-list"); - let emptyLabel = permissionsList.nextSibling; + let emptyLabel = permissionsList.nextSibling.nextSibling; yield openIdentityPopup(); @@ -107,7 +107,7 @@ add_task(function* testCancelPermission() { yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); let permissionsList = document.getElementById("identity-popup-permission-list"); - let emptyLabel = permissionsList.nextSibling; + let emptyLabel = permissionsList.nextSibling.nextSibling; SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW); SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK); From 64c8ce2febdbf07c5d1d0fb9bf9b5892d3bc73a0 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Mon, 12 Sep 2016 17:31:51 +0200 Subject: [PATCH 4/8] Bug 1300473 - Properly set recording buttons classes in performance tool. r=gregtatum --- .../components/recording-button.js | 12 +++++-- .../components/recording-controls.js | 34 +++++-------------- .../test/browser_perf-button-states.js | 32 ++++++++--------- devtools/client/themes/common.css | 16 +++++---- devtools/client/themes/performance.css | 9 ++--- 5 files changed, 46 insertions(+), 57 deletions(-) diff --git a/devtools/client/performance/components/recording-button.js b/devtools/client/performance/components/recording-button.js index 3fcf5d5476ce..877fd0e2b8ab 100644 --- a/devtools/client/performance/components/recording-button.js +++ b/devtools/client/performance/components/recording-button.js @@ -13,15 +13,23 @@ module.exports = createClass({ render() { let { onRecordButtonClick, - isRecording + isRecording, + isLocked } = this.props; + let classList = ["devtools-button", "record-button"]; + + if (isRecording) { + classList.push("checked"); + } + return button( { - className: "devtools-toolbarbutton record-button", + className: classList.join(" "), onClick: onRecordButtonClick, "data-standalone": "true", "data-text-only": "true", + disabled: isLocked }, isRecording ? L10N.getStr("recordings.stop") : L10N.getStr("recordings.start") ); diff --git a/devtools/client/performance/components/recording-controls.js b/devtools/client/performance/components/recording-controls.js index 5b83edcd583b..88f788ef387d 100644 --- a/devtools/client/performance/components/recording-controls.js +++ b/devtools/client/performance/components/recording-controls.js @@ -10,24 +10,6 @@ const {div, button} = DOM; module.exports = createClass({ displayName: "Recording Controls", - /** - * Manually handle the "checked" and "locked" attributes, as the DOM element won't - * change by just by changing the checked attribute through React. - */ - componentDidUpdate() { - if (this.props.isRecording) { - this._recordButton.setAttribute("checked", true); - } else { - this._recordButton.removeAttribute("checked"); - } - - if (this.props.isLocked) { - this._recordButton.setAttribute("locked", true); - } else { - this._recordButton.removeAttribute("locked"); - } - }, - render() { let { onClearButtonClick, @@ -37,6 +19,12 @@ module.exports = createClass({ isLocked } = this.props; + let recordButtonClassList = ["devtools-button", "record-button"]; + + if (isRecording) { + recordButtonClassList.push("checked"); + } + return ( div({ className: "devtools-toolbar" }, div({ className: "toolbar-group" }, @@ -48,14 +36,10 @@ module.exports = createClass({ }), button({ id: "main-record-button", - className: "devtools-button record-button", + className: recordButtonClassList.join(" "), + disabled: isLocked, title: L10N.getStr("recordings.start.tooltip"), - onClick: onRecordButtonClick, - checked: isRecording, - ref: (el) => { - this._recordButton = el; - }, - locked: isLocked + onClick: onRecordButtonClick }), button({ id: "import-button", diff --git a/devtools/client/performance/test/browser_perf-button-states.js b/devtools/client/performance/test/browser_perf-button-states.js index 672eceaab2ca..7f7ca1b2a463 100644 --- a/devtools/client/performance/test/browser_perf-button-states.js +++ b/devtools/client/performance/test/browser_perf-button-states.js @@ -16,13 +16,11 @@ add_task(function* () { win: window }); - let { $, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; + let { $, $$, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; + let recordButton = $("#main-record-button"); - ok(!recordButton.hasAttribute("checked"), - "The record button should not be checked yet."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked yet."); + checkRecordButtonsStates(false, false); let uiStartClick = once(PerformanceView, EVENTS.UI_START_RECORDING); let recordingStarted = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, { @@ -37,17 +35,11 @@ add_task(function* () { click(recordButton); yield uiStartClick; - ok(recordButton.hasAttribute("checked"), - "The record button should now be checked."); - ok(recordButton.hasAttribute("locked"), - "The record button should be locked."); + checkRecordButtonsStates(true, true); yield recordingStarted; - ok(recordButton.hasAttribute("checked"), - "The record button should still be checked."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked."); + checkRecordButtonsStates(true, false); yield backendStartReady; yield uiStateRecording; @@ -66,13 +58,19 @@ add_task(function* () { yield uiStopClick; yield recordingStopped; - ok(!recordButton.hasAttribute("checked"), - "The record button should not be checked."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked."); + checkRecordButtonsStates(false, false); yield backendStopReady; yield uiStateRecorded; yield teardownToolboxAndRemoveTab(panel); + + function checkRecordButtonsStates(checked, locked) { + for (let button of $$(".record-button")) { + is(button.classList.contains("checked"), checked, + "The record button checked state should be " + checked); + is(button.disabled, locked, + "The record button locked state should be " + locked); + } + } }); diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css index c0cea808d821..e79ca583cdeb 100644 --- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -367,6 +367,7 @@ checkbox:-moz-focusring { } .devtools-button:hover:empty::before, +.devtools-button.checked:empty::before, .devtools-button[checked]:empty::before, .devtools-button[open]:empty::before, .devtools-toolbarbutton:not([label]):hover > image, @@ -378,6 +379,7 @@ checkbox:-moz-focusring { .devtools-button:disabled, .devtools-button[disabled], .devtools-toolbarbutton[disabled] { + pointer-events: none; opacity: 0.5 !important; } @@ -397,29 +399,31 @@ checkbox:-moz-focusring { /* Text-only buttons */ .theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-light .devtools-toolbarbutton[data-text-only] { +.theme-light .devtools-toolbarbutton[data-text-only], +.theme-light .devtools-button:not(:empty) { background-color: var(--toolbar-tab-hover); } .theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-dark .devtools-toolbarbutton[data-text-only] { +.theme-dark .devtools-toolbarbutton[data-text-only], +.theme-dark .devtools-button:not(:empty) { background-color: rgba(0, 0, 0, .2); /* Splitter */ } /* Text-only button states */ -.theme-dark .devtools-button:not(:empty):not([disabled]):hover, +.theme-dark .devtools-button:not(:empty):not(:disabled):hover, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(0, 0, 0, .3); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not([disabled]):hover, +.theme-light .devtools-button:not(:empty):not(:disabled):hover, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(170, 170, 170, .3); /* Splitters */ } -.theme-dark .devtools-button:not(:empty):not([disabled]):hover:active, +.theme-dark .devtools-button:not(:empty):not(:disabled):hover:active, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: rgba(0, 0, 0, .4); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not([disabled]):hover:active, +.theme-light .devtools-button:not(:empty):not(:disabled):hover:active, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: var(--toolbar-tab-hover-active); } diff --git a/devtools/client/themes/performance.css b/devtools/client/themes/performance.css index 9349d690941b..142b8fa7e52c 100644 --- a/devtools/client/themes/performance.css +++ b/devtools/client/themes/performance.css @@ -116,17 +116,12 @@ padding: 5px !important; } -.notice-container .record-button[checked], -.notice-container .record-button[checked] { +.notice-container .record-button.checked, +.notice-container .record-button.checked { color: var(--theme-selection-color) !important; background: var(--theme-selection-background) !important; } -.record-button[locked] { - pointer-events: none; - opacity: 0.5; -} - /* Sidebar & recording items */ #recordings-pane { From ef97f74f532bcb15d3b68491e5f5ac7364815d7e Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Mon, 12 Sep 2016 18:46:18 +0200 Subject: [PATCH 5/8] Bug 1283042 - add a test for highlighting XML documents; needed to fix up highlightFinished notifications. r=jaws MozReview-Commit-ID: HKw51diGOBm --- toolkit/modules/Finder.jsm | 3 +- toolkit/modules/FinderHighlighter.jsm | 2 ++ .../browser/browser_FinderHighlighter.js | 33 +++++++++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/toolkit/modules/Finder.jsm b/toolkit/modules/Finder.jsm index 641659923a51..4c80fff21c01 100644 --- a/toolkit/modules/Finder.jsm +++ b/toolkit/modules/Finder.jsm @@ -238,8 +238,7 @@ Finder.prototype = { }, highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) { - let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly); - this.highlighter.notifyFinished({ highlight: aHighlight, found }); + yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly); }), getInitialSelection: function() { diff --git a/toolkit/modules/FinderHighlighter.jsm b/toolkit/modules/FinderHighlighter.jsm index 1bf8f866b810..894b13cb5585 100644 --- a/toolkit/modules/FinderHighlighter.jsm +++ b/toolkit/modules/FinderHighlighter.jsm @@ -224,6 +224,8 @@ FinderHighlighter.prototype = { this._found = true; } + this.notifyFinished({ highlight, found: this._found }); + return this._found; }), diff --git a/toolkit/modules/tests/browser/browser_FinderHighlighter.js b/toolkit/modules/tests/browser/browser_FinderHighlighter.js index 0783fbd827eb..e51603cc5f7a 100644 --- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js +++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js @@ -3,12 +3,15 @@ Cu.import("resource://testing-common/BrowserTestUtils.jsm", this); Cu.import("resource://testing-common/ContentTask.jsm", this); Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/Timer.jsm", this); Cu.import("resource://gre/modules/AppConstants.jsm"); const kHighlightAllPref = "findbar.highlightAll"; const kPrefModalHighlight = "findbar.modalHighlight"; const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/"; +const kIteratorTimeout = Services.prefs.getIntPref("findbar.iteratorTimeout"); function promiseOpenFindbar(findbar) { findbar.onFindCommand() @@ -155,8 +158,8 @@ add_task(function* testModalResults() { }], ["o", { rectCount: 492, - insertCalls: [1, 4], - removeCalls: [1, 3] + insertCalls: [1, 5], + removeCalls: [1, 4] }] ]); let url = kFixtureBaseURL + "file_FinderSample.html"; @@ -167,6 +170,7 @@ add_task(function* testModalResults() { yield promiseOpenFindbar(findbar); Assert.ok(!findbar.hidden, "Findbar should be open now."); + yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout)); let promise = promiseTestHighlighterOutput(browser, word, expectedResult); yield promiseEnterStringIntoFindField(findbar, word); yield promise; @@ -311,3 +315,28 @@ add_task(function* testHighlightAllToggle() { yield promise; }); }); + +add_task(function* testXMLDocument() { + let url = "data:text/xml;charset=utf-8," + encodeURIComponent(` + + Example + Error +`); + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Example"; + let expectedResult = { + rectCount: 0, + insertCalls: [1, 4], + removeCalls: [1, 2] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + }); +}); From 12f9c4e67f37347fa8a163069a3569dfdbc61532 Mon Sep 17 00:00:00 2001 From: Sebastian Hengst Date: Mon, 12 Sep 2016 20:03:26 +0200 Subject: [PATCH 6/8] Backed out changeset 5b64b2e8e518 (bug 1300473) for failing devtools test browser_perf-recording-selected-03.js. r=backout --- .../components/recording-button.js | 12 ++----- .../components/recording-controls.js | 34 ++++++++++++++----- .../test/browser_perf-button-states.js | 32 +++++++++-------- devtools/client/themes/common.css | 16 ++++----- devtools/client/themes/performance.css | 9 +++-- 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/devtools/client/performance/components/recording-button.js b/devtools/client/performance/components/recording-button.js index 877fd0e2b8ab..3fcf5d5476ce 100644 --- a/devtools/client/performance/components/recording-button.js +++ b/devtools/client/performance/components/recording-button.js @@ -13,23 +13,15 @@ module.exports = createClass({ render() { let { onRecordButtonClick, - isRecording, - isLocked + isRecording } = this.props; - let classList = ["devtools-button", "record-button"]; - - if (isRecording) { - classList.push("checked"); - } - return button( { - className: classList.join(" "), + className: "devtools-toolbarbutton record-button", onClick: onRecordButtonClick, "data-standalone": "true", "data-text-only": "true", - disabled: isLocked }, isRecording ? L10N.getStr("recordings.stop") : L10N.getStr("recordings.start") ); diff --git a/devtools/client/performance/components/recording-controls.js b/devtools/client/performance/components/recording-controls.js index 88f788ef387d..5b83edcd583b 100644 --- a/devtools/client/performance/components/recording-controls.js +++ b/devtools/client/performance/components/recording-controls.js @@ -10,6 +10,24 @@ const {div, button} = DOM; module.exports = createClass({ displayName: "Recording Controls", + /** + * Manually handle the "checked" and "locked" attributes, as the DOM element won't + * change by just by changing the checked attribute through React. + */ + componentDidUpdate() { + if (this.props.isRecording) { + this._recordButton.setAttribute("checked", true); + } else { + this._recordButton.removeAttribute("checked"); + } + + if (this.props.isLocked) { + this._recordButton.setAttribute("locked", true); + } else { + this._recordButton.removeAttribute("locked"); + } + }, + render() { let { onClearButtonClick, @@ -19,12 +37,6 @@ module.exports = createClass({ isLocked } = this.props; - let recordButtonClassList = ["devtools-button", "record-button"]; - - if (isRecording) { - recordButtonClassList.push("checked"); - } - return ( div({ className: "devtools-toolbar" }, div({ className: "toolbar-group" }, @@ -36,10 +48,14 @@ module.exports = createClass({ }), button({ id: "main-record-button", - className: recordButtonClassList.join(" "), - disabled: isLocked, + className: "devtools-button record-button", title: L10N.getStr("recordings.start.tooltip"), - onClick: onRecordButtonClick + onClick: onRecordButtonClick, + checked: isRecording, + ref: (el) => { + this._recordButton = el; + }, + locked: isLocked }), button({ id: "import-button", diff --git a/devtools/client/performance/test/browser_perf-button-states.js b/devtools/client/performance/test/browser_perf-button-states.js index 7f7ca1b2a463..672eceaab2ca 100644 --- a/devtools/client/performance/test/browser_perf-button-states.js +++ b/devtools/client/performance/test/browser_perf-button-states.js @@ -16,11 +16,13 @@ add_task(function* () { win: window }); - let { $, $$, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; - + let { $, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; let recordButton = $("#main-record-button"); - checkRecordButtonsStates(false, false); + ok(!recordButton.hasAttribute("checked"), + "The record button should not be checked yet."); + ok(!recordButton.hasAttribute("locked"), + "The record button should not be locked yet."); let uiStartClick = once(PerformanceView, EVENTS.UI_START_RECORDING); let recordingStarted = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, { @@ -35,11 +37,17 @@ add_task(function* () { click(recordButton); yield uiStartClick; - checkRecordButtonsStates(true, true); + ok(recordButton.hasAttribute("checked"), + "The record button should now be checked."); + ok(recordButton.hasAttribute("locked"), + "The record button should be locked."); yield recordingStarted; - checkRecordButtonsStates(true, false); + ok(recordButton.hasAttribute("checked"), + "The record button should still be checked."); + ok(!recordButton.hasAttribute("locked"), + "The record button should not be locked."); yield backendStartReady; yield uiStateRecording; @@ -58,19 +66,13 @@ add_task(function* () { yield uiStopClick; yield recordingStopped; - checkRecordButtonsStates(false, false); + ok(!recordButton.hasAttribute("checked"), + "The record button should not be checked."); + ok(!recordButton.hasAttribute("locked"), + "The record button should not be locked."); yield backendStopReady; yield uiStateRecorded; yield teardownToolboxAndRemoveTab(panel); - - function checkRecordButtonsStates(checked, locked) { - for (let button of $$(".record-button")) { - is(button.classList.contains("checked"), checked, - "The record button checked state should be " + checked); - is(button.disabled, locked, - "The record button locked state should be " + locked); - } - } }); diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css index e79ca583cdeb..c0cea808d821 100644 --- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -367,7 +367,6 @@ checkbox:-moz-focusring { } .devtools-button:hover:empty::before, -.devtools-button.checked:empty::before, .devtools-button[checked]:empty::before, .devtools-button[open]:empty::before, .devtools-toolbarbutton:not([label]):hover > image, @@ -379,7 +378,6 @@ checkbox:-moz-focusring { .devtools-button:disabled, .devtools-button[disabled], .devtools-toolbarbutton[disabled] { - pointer-events: none; opacity: 0.5 !important; } @@ -399,31 +397,29 @@ checkbox:-moz-focusring { /* Text-only buttons */ .theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-light .devtools-toolbarbutton[data-text-only], -.theme-light .devtools-button:not(:empty) { +.theme-light .devtools-toolbarbutton[data-text-only] { background-color: var(--toolbar-tab-hover); } .theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-dark .devtools-toolbarbutton[data-text-only], -.theme-dark .devtools-button:not(:empty) { +.theme-dark .devtools-toolbarbutton[data-text-only] { background-color: rgba(0, 0, 0, .2); /* Splitter */ } /* Text-only button states */ -.theme-dark .devtools-button:not(:empty):not(:disabled):hover, +.theme-dark .devtools-button:not(:empty):not([disabled]):hover, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(0, 0, 0, .3); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not(:disabled):hover, +.theme-light .devtools-button:not(:empty):not([disabled]):hover, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(170, 170, 170, .3); /* Splitters */ } -.theme-dark .devtools-button:not(:empty):not(:disabled):hover:active, +.theme-dark .devtools-button:not(:empty):not([disabled]):hover:active, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: rgba(0, 0, 0, .4); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not(:disabled):hover:active, +.theme-light .devtools-button:not(:empty):not([disabled]):hover:active, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: var(--toolbar-tab-hover-active); } diff --git a/devtools/client/themes/performance.css b/devtools/client/themes/performance.css index 142b8fa7e52c..9349d690941b 100644 --- a/devtools/client/themes/performance.css +++ b/devtools/client/themes/performance.css @@ -116,12 +116,17 @@ padding: 5px !important; } -.notice-container .record-button.checked, -.notice-container .record-button.checked { +.notice-container .record-button[checked], +.notice-container .record-button[checked] { color: var(--theme-selection-color) !important; background: var(--theme-selection-background) !important; } +.record-button[locked] { + pointer-events: none; + opacity: 0.5; +} + /* Sidebar & recording items */ #recordings-pane { From 7a284cc99773285cc29314b1ada36c737b69fcfc Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 12 Sep 2016 14:20:22 -0400 Subject: [PATCH 7/8] Bug 1301487 - Update the debugger bundle and implement a proper shutdown method r=me UPDATE_BUNDLE --- devtools/client/debugger/new/bundle.js | 32493 +--------------- devtools/client/debugger/new/panel.js | 2 + .../debugger/new/pretty-print-worker.js | 62 +- .../client/debugger/new/source-map-worker.js | 240 +- devtools/client/debugger/new/styles.css | 2758 +- .../debugger/new/test/mochitest/.eslintrc | 64 + .../debugger/new/test/mochitest/browser.ini | 25 +- .../test/mochitest/browser_dbg-call-stack.js | 33 + .../mochitest/browser_dbg-chrome-create.js | 72 + .../mochitest/browser_dbg-chrome-debugging.js | 88 + .../mochitest/browser_dbg-debugger-buttons.js | 57 + .../mochitest/browser_dbg-editor-gutter.js | 64 + .../test/mochitest/browser_dbg-editor-mode.js | 17 + .../mochitest/browser_dbg-editor-select.js | 52 + .../new/test/mochitest/browser_dbg-iframes.js | 26 + .../mochitest/browser_dbg-pause-exceptions.js | 46 + .../new/test/mochitest/browser_dbg_stub.js | 7 - .../new/test/mochitest/examples/README.md | 7 + .../mochitest/examples/code-exceptions.js | 19 + .../new/test/mochitest/examples/code-long.js | 76 + .../examples/code-script-switching-01.js | 6 + .../examples/code-script-switching-02.js | 13 + .../test/mochitest/examples/code-simple1.js | 7 + .../test/mochitest/examples/code-simple2.js | 6 + .../examples/doc-debugger-statements.html | 27 + .../mochitest/examples/doc-exceptions.html | 7 + .../test/mochitest/examples/doc-iframes.html | 17 + .../examples/doc-script-switching-01.html | 18 + .../test/mochitest/examples/doc-scripts.html | 21 + .../debugger/new/test/mochitest/head.js | 316 + 30 files changed, 2750 insertions(+), 33896 deletions(-) create mode 100644 devtools/client/debugger/new/test/mochitest/.eslintrc create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js create mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js delete mode 100644 devtools/client/debugger/new/test/mochitest/browser_dbg_stub.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/README.md create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-exceptions.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-long.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-script-switching-01.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-script-switching-02.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-simple1.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/code-simple2.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html create mode 100644 devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html create mode 100644 devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html create mode 100644 devtools/client/debugger/new/test/mochitest/examples/doc-script-switching-01.html create mode 100644 devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html create mode 100644 devtools/client/debugger/new/test/mochitest/head.js diff --git a/devtools/client/debugger/new/bundle.js b/devtools/client/debugger/new/bundle.js index 9d739106b6ad..54aa35bb1a56 100644 --- a/devtools/client/debugger/new/bundle.js +++ b/devtools/client/debugger/new/bundle.js @@ -1,4 +1,4 @@ -// Generated from: 9c498c2c83e7562909132883e12578a9b45fc295 Fix #653, finesse selected tab designs (#654) +// Generated from: 02b44328ac19e43705f51209237b20fb264f93f4 Merge pull request #711 from devtools-html/cleanup var Debugger = /******/ (function(modules) { // webpackBootstrap @@ -65,17 +65,17 @@ var Debugger = var Provider = _require2.Provider; - var ReactDOM = __webpack_require__(176); + var ReactDOM = __webpack_require__(25); var React = __webpack_require__(17); - var DevToolsUtils = __webpack_require__(177); - var AppConstants = __webpack_require__(191).AppConstants; + var DevToolsUtils = __webpack_require__(27); + var AppConstants = __webpack_require__(41).AppConstants; - var _require3 = __webpack_require__(195); + var _require3 = __webpack_require__(45); var injectGlobals = _require3.injectGlobals; - var _require4 = __webpack_require__(197); + var _require4 = __webpack_require__(47); var isEnabled = _require4.isEnabled; var isFirefoxPanel = _require4.isFirefoxPanel; @@ -84,26 +84,26 @@ var Debugger = var setConfig = _require4.setConfig; - setConfig(({"environment":"firefox-panel","baseWorkerURL":"resource://devtools/client/debugger/new/","logging":{"client":false,"firefoxProxy":false,"actions":false},"clientLogging":false,"features":{"tabs":true,"sourceMaps":false,"prettyPrint":true,"watchExpressions":false},"hotReloading":true})); + setConfig(({"environment":"firefox-panel","baseWorkerURL":"resource://devtools/client/debugger/new/","logging":false,"clientLogging":false,"features":{"tabs":true}})); // Set various flags before requiring app code. if (isEnabled("logging.client")) { DevToolsUtils.dumpn.wantLogging = true; } - var _require5 = __webpack_require__(245); + var _require5 = __webpack_require__(96); var getClient = _require5.getClient; var connectClients = _require5.connectClients; var startDebugging = _require5.startDebugging; - var firefox = __webpack_require__(247); - var configureStore = __webpack_require__(328); - var reducers = __webpack_require__(336); - var selectors = __webpack_require__(347); + var firefox = __webpack_require__(98); + var configureStore = __webpack_require__(179); + var reducers = __webpack_require__(187); + var selectors = __webpack_require__(198); - var Tabs = __webpack_require__(354); - var App = __webpack_require__(360); + var Tabs = __webpack_require__(205); + var App = __webpack_require__(211); var createStore = configureStore({ log: getValue("logging.actions"), @@ -113,7 +113,7 @@ var Debugger = }); var store = createStore(combineReducers(reducers)); - var actions = bindActionCreators(__webpack_require__(362), store.dispatch); + var actions = bindActionCreators(__webpack_require__(213), store.dispatch); if (isDevelopment()) { AppConstants.DEBUG_JS_MODULES = true; @@ -138,6 +138,11 @@ var Debugger = ReactDOM.render(React.createElement(Provider, { store }, React.createElement(component)), mount); } + function unmountRoot() { + var mount = document.querySelector("#mount"); + ReactDOM.unmountComponentAtNode(mount); + } + function getTargetFromQuery() { var href = window.location.href; var nodeMatch = href.match(/ws=([^&#]*)/); @@ -164,29 +169,26 @@ var Debugger = }); } else if (isFirefoxPanel()) { (function () { - // The toolbox already provides the tab to debug. - function bootstrap(_ref) { - var threadClient = _ref.threadClient; - var tabTarget = _ref.tabTarget; - - firefox.setThreadClient(threadClient); - firefox.setTabTarget(tabTarget); - firefox.initPage(actions); - renderRoot(App); - } + var sourceMap = __webpack_require__(215); module.exports = { - bootstrap, + bootstrap: _ref => { + var threadClient = _ref.threadClient; + var tabTarget = _ref.tabTarget; + + firefox.setThreadClient(threadClient); + firefox.setTabTarget(tabTarget); + firefox.initPage(actions); + renderRoot(App); + }, + destroy: () => { + unmountRoot(); + sourceMap.destroy(); + }, store: store, actions: actions, selectors: selectors, - - // Remove these once we update the API on m-c - setThreadClient: firefox.setThreadClient, - setTabTarget: firefox.setTabTarget, - initPage: firefox.initPage, - renderApp: () => renderRoot(App), - getActions: () => actions + client: firefox.clientCommands }; })(); } else { @@ -1047,7 +1049,7 @@ var Debugger = var _Provider2 = _interopRequireDefault(_Provider); - var _connect = __webpack_require__(171); + var _connect = __webpack_require__(20); var _connect2 = _interopRequireDefault(_connect); @@ -1067,11 +1069,11 @@ var Debugger = var _react = __webpack_require__(17); - var _storeShape = __webpack_require__(169); + var _storeShape = __webpack_require__(18); var _storeShape2 = _interopRequireDefault(_storeShape); - var _warning = __webpack_require__(170); + var _warning = __webpack_require__(19); var _warning2 = _interopRequireDefault(_warning); @@ -1141,18923 +1143,12 @@ var Debugger = /***/ }, /* 17 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - module.exports = __webpack_require__(18); +/***/ function(module, exports) { + module.exports = devtoolsRequire('devtools/client/shared/vendor/react'); /***/ }, /* 18 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 React - */ - - 'use strict'; - - var ReactDOM = __webpack_require__(19); - var ReactDOMServer = __webpack_require__(159); - var ReactIsomorphic = __webpack_require__(163); - - var assign = __webpack_require__(54); - var deprecated = __webpack_require__(168); - - // `version` will be added here by ReactIsomorphic. - var React = {}; - - assign(React, ReactIsomorphic); - - assign(React, { - // ReactDOM - findDOMNode: deprecated('findDOMNode', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.findDOMNode), - render: deprecated('render', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.render), - unmountComponentAtNode: deprecated('unmountComponentAtNode', 'ReactDOM', 'react-dom', ReactDOM, ReactDOM.unmountComponentAtNode), - - // ReactDOMServer - renderToString: deprecated('renderToString', 'ReactDOMServer', 'react-dom/server', ReactDOMServer, ReactDOMServer.renderToString), - renderToStaticMarkup: deprecated('renderToStaticMarkup', 'ReactDOMServer', 'react-dom/server', ReactDOMServer, ReactDOMServer.renderToStaticMarkup) - }); - - React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOM; - React.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOMServer; - - module.exports = React; - -/***/ }, -/* 19 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(20); - var ReactDOMTextComponent = __webpack_require__(21); - var ReactDefaultInjection = __webpack_require__(86); - var ReactInstanceHandles = __webpack_require__(60); - var ReactMount = __webpack_require__(43); - var ReactPerf = __webpack_require__(33); - var ReactReconciler = __webpack_require__(65); - var ReactUpdates = __webpack_require__(69); - var ReactVersion = __webpack_require__(157); - - var findDOMNode = __webpack_require__(106); - var renderSubtreeIntoContainer = __webpack_require__(158); - var warning = __webpack_require__(40); - - 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 (false) { - var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); - 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; - - process.env.NODE_ENV !== '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; - -/***/ }, -/* 20 */ -/***/ function(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; - -/***/ }, -/* 21 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 ReactDOMTextComponent - * @typechecks static-only - */ - - 'use strict'; - - var DOMChildrenOperations = __webpack_require__(22); - var DOMPropertyOperations = __webpack_require__(37); - var ReactComponentBrowserEnvironment = __webpack_require__(41); - var ReactMount = __webpack_require__(43); - - var assign = __webpack_require__(54); - var escapeTextContentForBrowser = __webpack_require__(36); - var setTextContent = __webpack_require__(35); - var validateDOMNesting = __webpack_require__(85); - - /** - * Text nodes violate a couple assumptions that React makes about components: - * - * - When mounting text into the DOM, adjacent text nodes are merged. - * - Text nodes cannot be assigned a React root ID. - * - * This component is used to wrap strings in elements so that they can undergo - * the same reconciliation that is applied to elements. - * - * TODO: Investigate representing React components in the DOM with text nodes. - * - * @class ReactDOMTextComponent - * @extends ReactComponent - * @internal - */ - var ReactDOMTextComponent = function (props) { - // This constructor and its argument is currently used by mocks. - }; - - assign(ReactDOMTextComponent.prototype, { - - /** - * @param {ReactText} text - * @internal - */ - construct: function (text) { - // TODO: This is really a ReactText (ReactNode), not a ReactElement - this._currentElement = text; - this._stringText = '' + text; - - // Properties - this._rootNodeID = null; - this._mountIndex = 0; - }, - - /** - * Creates the markup for this text node. This node is not intended to have - * any features besides containing text content. - * - * @param {string} rootID DOM ID of the root node. - * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction - * @return {string} Markup for this text node. - * @internal - */ - mountComponent: function (rootID, transaction, context) { - if (false) { - if (context[validateDOMNesting.ancestorInfoContextKey]) { - validateDOMNesting('span', null, context[validateDOMNesting.ancestorInfoContextKey]); - } - } - - this._rootNodeID = rootID; - if (transaction.useCreateElement) { - var ownerDocument = context[ReactMount.ownerDocumentContextKey]; - var el = ownerDocument.createElement('span'); - DOMPropertyOperations.setAttributeForID(el, rootID); - // Populate node cache - ReactMount.getID(el); - setTextContent(el, this._stringText); - return el; - } else { - var escapedText = escapeTextContentForBrowser(this._stringText); - - if (transaction.renderToStaticMarkup) { - // Normally we'd wrap this in a `span` for the reasons stated above, but - // since this is a situation where React won't take over (static pages), - // we can simply return the text as it is. - return escapedText; - } - - return '' + escapedText + ''; - } - }, - - /** - * Updates this component by updating the text content. - * - * @param {ReactText} nextText The next text content - * @param {ReactReconcileTransaction} transaction - * @internal - */ - receiveComponent: function (nextText, transaction) { - if (nextText !== this._currentElement) { - this._currentElement = nextText; - var nextStringText = '' + nextText; - if (nextStringText !== this._stringText) { - // TODO: Save this as pending props and use performUpdateIfNecessary - // and/or updateComponent to do the actual update for consistency with - // other component types? - this._stringText = nextStringText; - var node = ReactMount.getNode(this._rootNodeID); - DOMChildrenOperations.updateTextContent(node, nextStringText); - } - } - }, - - unmountComponent: function () { - ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID); - } - - }); - - module.exports = ReactDOMTextComponent; - -/***/ }, -/* 22 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(23); - var ReactMultiChildUpdateTypes = __webpack_require__(31); - var ReactPerf = __webpack_require__(33); - - var setInnerHTML = __webpack_require__(34); - var setTextContent = __webpack_require__(35); - var invariant = __webpack_require__(28); - - /** - * 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 ? false ? 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; - -/***/ }, -/* 23 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(24); - - var createNodesFromMarkup = __webpack_require__(25); - var emptyFunction = __webpack_require__(30); - var getMarkupWrap = __webpack_require__(29); - var invariant = __webpack_require__(28); - - 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 ? false ? 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] ? false ? 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 ', '
']; - var trWrap = [3, '', '
']; - - var svgWrap = [1, '', '']; - - var markupWrap = { - '*': [1, '?

'], - - 'area': [1, '', ''], - 'col': [2, '', '
'], - 'legend': [1, '
', '
'], - 'param': [1, '', ''], - 'tr': [2, '', '
'], - - 'optgroup': selectWrap, - 'option': selectWrap, - - 'caption': tableWrap, - 'colgroup': tableWrap, - 'tbody': tableWrap, - 'tfoot': tableWrap, - 'thead': tableWrap, - - 'td': trWrap, - 'th': trWrap - }; - - // Initialize the SVG elements since we know they'll always need to be wrapped - // consistently. If they are created inside a
they will be initialized in - // the wrong namespace (and will not display). - var svgElements = ['circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'text', 'tspan']; - svgElements.forEach(function (nodeName) { - markupWrap[nodeName] = svgWrap; - shouldWrap[nodeName] = true; - }); - - /** - * Gets the markup wrap configuration for the supplied `nodeName`. - * - * NOTE: This lazily detects which wraps are necessary for the current browser. - * - * @param {string} nodeName Lowercase `nodeName`. - * @return {?array} Markup wrap configuration, if applicable. - */ - function getMarkupWrap(nodeName) { - !!!dummyNode ? false ? invariant(false, 'Markup wrapping node not initialized') : invariant(false) : undefined; - if (!markupWrap.hasOwnProperty(nodeName)) { - nodeName = '*'; - } - if (!shouldWrap.hasOwnProperty(nodeName)) { - if (nodeName === '*') { - dummyNode.innerHTML = ''; - } else { - dummyNode.innerHTML = '<' + nodeName + '>'; - } - shouldWrap[nodeName] = !dummyNode.firstChild; - } - return shouldWrap[nodeName] ? markupWrap[nodeName] : null; - } - - module.exports = getMarkupWrap; - -/***/ }, -/* 30 */ -/***/ function(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 emptyFunction - */ - - "use strict"; - - function makeEmptyFunction(arg) { - return function () { - return arg; - }; - } - - /** - * This function accepts and discards inputs; it has no side effects. This is - * primarily useful idiomatically for overridable function endpoints which - * always need to be callable, since JS lacks a null-call idiom ala Cocoa. - */ - function emptyFunction() {} - - emptyFunction.thatReturns = makeEmptyFunction; - emptyFunction.thatReturnsFalse = makeEmptyFunction(false); - emptyFunction.thatReturnsTrue = makeEmptyFunction(true); - emptyFunction.thatReturnsNull = makeEmptyFunction(null); - emptyFunction.thatReturnsThis = function () { - return this; - }; - emptyFunction.thatReturnsArgument = function (arg) { - return arg; - }; - - module.exports = emptyFunction; - -/***/ }, -/* 31 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 ReactMultiChildUpdateTypes - */ - - 'use strict'; - - var keyMirror = __webpack_require__(32); - - /** - * When a component's children are updated, a series of update configuration - * objects are created in order to batch and serialize the required changes. - * - * Enumerates all the possible types of update configurations. - * - * @internal - */ - var ReactMultiChildUpdateTypes = keyMirror({ - INSERT_MARKUP: null, - MOVE_EXISTING: null, - REMOVE_NODE: null, - SET_MARKUP: null, - TEXT_CONTENT: null - }); - - module.exports = ReactMultiChildUpdateTypes; - -/***/ }, -/* 32 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 keyMirror - * @typechecks static-only - */ - - 'use strict'; - - var invariant = __webpack_require__(28); - - /** - * Constructs an enumeration with keys equal to their value. - * - * For example: - * - * var COLORS = keyMirror({blue: null, red: null}); - * var myColor = COLORS.blue; - * var isColorValid = !!COLORS[myColor]; - * - * The last line could not be performed if the values of the generated enum were - * not equal to their keys. - * - * Input: {key1: val1, key2: val2} - * Output: {key1: key1, key2: key2} - * - * @param {object} obj - * @return {object} - */ - var keyMirror = function (obj) { - var ret = {}; - var key; - !(obj instanceof Object && !Array.isArray(obj)) ? false ? invariant(false, 'keyMirror(...): Argument must be an object.') : invariant(false) : undefined; - for (key in obj) { - if (!obj.hasOwnProperty(key)) { - continue; - } - ret[key] = key; - } - return ret; - }; - - module.exports = keyMirror; - -/***/ }, -/* 33 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 ReactPerf - * @typechecks static-only - */ - - 'use strict'; - - /** - * ReactPerf is a general AOP system designed to measure performance. This - * module only has the hooks: see ReactDefaultPerf for the analysis tool. - */ - var ReactPerf = { - /** - * Boolean to enable/disable measurement. Set to false by default to prevent - * accidental logging and perf loss. - */ - enableMeasure: false, - - /** - * Holds onto the measure function in use. By default, don't measure - * anything, but we'll override this if we inject a measure function. - */ - storedMeasure: _noMeasure, - - /** - * @param {object} object - * @param {string} objectName - * @param {object} methodNames - */ - measureMethods: function (object, objectName, methodNames) { - if (false) { - for (var key in methodNames) { - if (!methodNames.hasOwnProperty(key)) { - continue; - } - object[key] = ReactPerf.measure(objectName, methodNames[key], object[key]); - } - } - }, - - /** - * Use this to wrap methods you want to measure. Zero overhead in production. - * - * @param {string} objName - * @param {string} fnName - * @param {function} func - * @return {function} - */ - measure: function (objName, fnName, func) { - if (false) { - var measuredFunc = null; - var wrapper = function () { - if (ReactPerf.enableMeasure) { - if (!measuredFunc) { - measuredFunc = ReactPerf.storedMeasure(objName, fnName, func); - } - return measuredFunc.apply(this, arguments); - } - return func.apply(this, arguments); - }; - wrapper.displayName = objName + '_' + fnName; - return wrapper; - } - return func; - }, - - injection: { - /** - * @param {function} measure - */ - injectMeasure: function (measure) { - ReactPerf.storedMeasure = measure; - } - } - }; - - /** - * Simply passes through the measured function, without measuring it. - * - * @param {string} objName - * @param {string} fnName - * @param {function} func - * @return {function} - */ - function _noMeasure(objName, fnName, func) { - return func; - } - - module.exports = ReactPerf; - -/***/ }, -/* 34 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 setInnerHTML - */ - - /* globals MSApp */ - - 'use strict'; - - var ExecutionEnvironment = __webpack_require__(24); - - var WHITESPACE_TEST = /^[ \r\n\t\f]/; - var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/; - - /** - * Set the innerHTML property of a node, ensuring that whitespace is preserved - * even in IE8. - * - * @param {DOMElement} node - * @param {string} html - * @internal - */ - var setInnerHTML = function (node, html) { - node.innerHTML = html; - }; - - // Win8 apps: Allow all html to be inserted - if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) { - setInnerHTML = function (node, html) { - MSApp.execUnsafeLocalFunction(function () { - node.innerHTML = html; - }); - }; - } - - if (ExecutionEnvironment.canUseDOM) { - // IE8: When updating a just created node with innerHTML only leading - // whitespace is removed. When updating an existing node with innerHTML - // whitespace in root TextNodes is also collapsed. - // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html - - // Feature detection; only IE8 is known to behave improperly like this. - var testElement = document.createElement('div'); - testElement.innerHTML = ' '; - if (testElement.innerHTML === '') { - setInnerHTML = function (node, html) { - // Magic theory: IE8 supposedly differentiates between added and updated - // nodes when processing innerHTML, innerHTML on updated nodes suffers - // from worse whitespace behavior. Re-adding a node like this triggers - // the initial and more favorable whitespace behavior. - // TODO: What to do on a detached node? - if (node.parentNode) { - node.parentNode.replaceChild(node, node); - } - - // We also implement a workaround for non-visible tags disappearing into - // thin air on IE8, this only happens if there is no visible text - // in-front of the non-visible tags. Piggyback on the whitespace fix - // and simply check if any non-visible tags appear in the source. - if (WHITESPACE_TEST.test(html) || html[0] === '<' && NONVISIBLE_TEST.test(html)) { - // Recover leading whitespace by temporarily prepending any character. - // \uFEFF has the potential advantage of being zero-width/invisible. - // UglifyJS drops U+FEFF chars when parsing, so use String.fromCharCode - // in hopes that this is preserved even if "\uFEFF" is transformed to - // the actual Unicode character (by Babel, for example). - // https://github.com/mishoo/UglifyJS2/blob/v2.4.20/lib/parse.js#L216 - node.innerHTML = String.fromCharCode(0xFEFF) + html; - - // deleteData leaves an empty `TextNode` which offsets the index of all - // children. Definitely want to avoid this. - var textNode = node.firstChild; - if (textNode.data.length === 1) { - node.removeChild(textNode); - } else { - textNode.deleteData(0, 1); - } - } else { - node.innerHTML = html; - } - }; - } - } - - module.exports = setInnerHTML; - -/***/ }, -/* 35 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 setTextContent - */ - - 'use strict'; - - var ExecutionEnvironment = __webpack_require__(24); - var escapeTextContentForBrowser = __webpack_require__(36); - var setInnerHTML = __webpack_require__(34); - - /** - * Set the textContent property of a node, ensuring that whitespace is preserved - * even in IE8. innerText is a poor substitute for textContent and, among many - * issues, inserts
instead of the literal newline chars. innerHTML behaves - * as it should. - * - * @param {DOMElement} node - * @param {string} text - * @internal - */ - var setTextContent = function (node, text) { - node.textContent = text; - }; - - if (ExecutionEnvironment.canUseDOM) { - if (!('textContent' in document.documentElement)) { - setTextContent = function (node, text) { - setInnerHTML(node, escapeTextContentForBrowser(text)); - }; - } - } - - module.exports = setTextContent; - -/***/ }, -/* 36 */ -/***/ function(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 escapeTextContentForBrowser - */ - - 'use strict'; - - var ESCAPE_LOOKUP = { - '&': '&', - '>': '>', - '<': '<', - '"': '"', - '\'': ''' - }; - - var ESCAPE_REGEX = /[&><"']/g; - - function escaper(match) { - return ESCAPE_LOOKUP[match]; - } - - /** - * Escapes text to prevent scripting attacks. - * - * @param {*} text Text value to escape. - * @return {string} An escaped string. - */ - function escapeTextContentForBrowser(text) { - return ('' + text).replace(ESCAPE_REGEX, escaper); - } - - module.exports = escapeTextContentForBrowser; - -/***/ }, -/* 37 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(38); - var ReactPerf = __webpack_require__(33); - - var quoteAttributeValueForBrowser = __webpack_require__(39); - var warning = __webpack_require__(40); - - // 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; - false ? 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 (false) { - 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. - process.env.NODE_ENV !== '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 (false) { - 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 (false) { - 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 (false) { - warnUnknownProperty(name); - } - } - - }; - - ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', { - setValueForProperty: 'setValueForProperty', - setValueForAttribute: 'setValueForAttribute', - deleteValueForProperty: 'deleteValueForProperty' - }); - - module.exports = DOMPropertyOperations; - -/***/ }, -/* 38 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(28); - - 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) ? false ? 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) ? false ? invariant(false, 'DOMProperty: Cannot require using both attribute and property: %s', propName) : invariant(false) : undefined; - !(propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects) ? false ? invariant(false, 'DOMProperty: Properties that have side effects must use property: %s', propName) : invariant(false) : undefined; - !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? false ? invariant(false, 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + 'numeric value, but not a combination: %s', propName) : invariant(false) : undefined; - - if (false) { - DOMProperty.getPossibleStandardName[lowerCased] = propName; - } - - if (DOMAttributeNames.hasOwnProperty(propName)) { - var attributeName = DOMAttributeNames[propName]; - propertyInfo.attributeName = attributeName; - if (false) { - 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: false ? {} : 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.createElement(nodeName); - nodeDefaults[prop] = testElement[prop]; - } - return nodeDefaults[prop]; - }, - - injection: DOMPropertyInjection - }; - - module.exports = DOMProperty; - -/***/ }, -/* 39 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 quoteAttributeValueForBrowser - */ - - 'use strict'; - - var escapeTextContentForBrowser = __webpack_require__(36); - - /** - * Escapes attribute value to prevent scripting attacks. - * - * @param {*} value Value to escape. - * @return {string} An escaped string. - */ - function quoteAttributeValueForBrowser(value) { - return '"' + escapeTextContentForBrowser(value) + '"'; - } - - module.exports = quoteAttributeValueForBrowser; - -/***/ }, -/* 40 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 warning - */ - - 'use strict'; - - var emptyFunction = __webpack_require__(30); - - /** - * Similar to invariant but only logs a warning if the condition is not met. - * This can be used to log issues in development environments in critical - * paths. Removing the logging code for production environments will keep the - * same logic and follow the same code paths. - */ - - var warning = emptyFunction; - - if (false) { - warning = function (condition, format) { - for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { - args[_key - 2] = arguments[_key]; - } - - if (format === undefined) { - throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument'); - } - - if (format.indexOf('Failed Composite propType: ') === 0) { - return; // Ignore CompositeComponent proptype check. - } - - if (!condition) { - var argIndex = 0; - var message = 'Warning: ' + format.replace(/%s/g, function () { - return args[argIndex++]; - }); - if (typeof console !== 'undefined') { - console.error(message); - } - try { - // --- Welcome to debugging React --- - // This error was thrown as a convenience so that you can use this stack - // to find the callsite that caused this warning to fire. - throw new Error(message); - } catch (x) {} - } - }; - } - - module.exports = warning; - -/***/ }, -/* 41 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 = __webpack_require__(42); - var ReactMount = __webpack_require__(43); - - /** - * 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; - -/***/ }, -/* 42 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 ReactDOMIDOperations - * @typechecks static-only - */ - - 'use strict'; - - var DOMChildrenOperations = __webpack_require__(22); - var DOMPropertyOperations = __webpack_require__(37); - var ReactMount = __webpack_require__(43); - var ReactPerf = __webpack_require__(33); - - var invariant = __webpack_require__(28); - - /** - * Errors for properties that should not be updated with `updatePropertyByID()`. - * - * @type {object} - * @private - */ - var INVALID_PROPERTY_ERRORS = { - dangerouslySetInnerHTML: '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.', - style: '`style` must be set using `updateStylesByID()`.' - }; - - /** - * Operations used to process updates to DOM nodes. - */ - var ReactDOMIDOperations = { - - /** - * Updates a DOM node with new property values. This should only be used to - * update DOM properties in `DOMProperty`. - * - * @param {string} id ID of the node to update. - * @param {string} name A valid property name, see `DOMProperty`. - * @param {*} value New value of the property. - * @internal - */ - updatePropertyByID: function (id, name, value) { - var node = ReactMount.getNode(id); - !!INVALID_PROPERTY_ERRORS.hasOwnProperty(name) ? false ? invariant(false, 'updatePropertyByID(...): %s', INVALID_PROPERTY_ERRORS[name]) : invariant(false) : undefined; - - // If we're updating to null or undefined, we should remove the property - // from the DOM node instead of inadvertantly setting to a string. This - // brings us in line with the same behavior we have on initial render. - if (value != null) { - DOMPropertyOperations.setValueForProperty(node, name, value); - } else { - DOMPropertyOperations.deleteValueForProperty(node, name); - } - }, - - /** - * Replaces a DOM node that exists in the document with markup. - * - * @param {string} id ID of child to be replaced. - * @param {string} markup Dangerous markup to inject in place of child. - * @internal - * @see {Danger.dangerouslyReplaceNodeWithMarkup} - */ - dangerouslyReplaceNodeWithMarkupByID: function (id, markup) { - var node = ReactMount.getNode(id); - DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup); - }, - - /** - * Updates a component's children by processing a series of updates. - * - * @param {array} updates List of update configurations. - * @param {array} markup List of markup strings. - * @internal - */ - dangerouslyProcessChildrenUpdates: function (updates, markup) { - for (var i = 0; i < updates.length; i++) { - updates[i].parentNode = ReactMount.getNode(updates[i].parentID); - } - DOMChildrenOperations.processUpdates(updates, markup); - } - }; - - ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', { - dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID', - dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates' - }); - - module.exports = ReactDOMIDOperations; - -/***/ }, -/* 43 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * 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 ReactMount - */ - - 'use strict'; - - var DOMProperty = __webpack_require__(38); - var ReactBrowserEventEmitter = __webpack_require__(44); - var ReactCurrentOwner = __webpack_require__(20); - var ReactDOMFeatureFlags = __webpack_require__(56); - var ReactElement = __webpack_require__(57); - var ReactEmptyComponentRegistry = __webpack_require__(59); - var ReactInstanceHandles = __webpack_require__(60); - var ReactInstanceMap = __webpack_require__(62); - var ReactMarkupChecksum = __webpack_require__(63); - var ReactPerf = __webpack_require__(33); - var ReactReconciler = __webpack_require__(65); - var ReactUpdateQueue = __webpack_require__(68); - var ReactUpdates = __webpack_require__(69); - - var assign = __webpack_require__(54); - var emptyObject = __webpack_require__(73); - var containsNode = __webpack_require__(74); - var instantiateReactComponent = __webpack_require__(77); - var invariant = __webpack_require__(28); - var setInnerHTML = __webpack_require__(34); - var shouldUpdateReactComponent = __webpack_require__(82); - var validateDOMNesting = __webpack_require__(85); - var warning = __webpack_require__(40); - - var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; - var nodeCache = {}; - - var ELEMENT_NODE_TYPE = 1; - var DOC_NODE_TYPE = 9; - var DOCUMENT_FRAGMENT_NODE_TYPE = 11; - - var ownerDocumentContextKey = '__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2); - - /** Mapping from reactRootID to React component instance. */ - var instancesByReactRootID = {}; - - /** Mapping from reactRootID to `container` nodes. */ - var containersByReactRootID = {}; - - if (false) { - /** __DEV__-only mapping from reactRootID to root elements. */ - var rootElementsByReactRootID = {}; - } - - // Used to store breadth-first search state in findComponentRoot. - var findComponentRootReusableArray = []; - - /** - * Finds the index of the first character - * that's not common between the two given strings. - * - * @return {number} the index of the character where the strings diverge - */ - function firstDifferenceIndex(string1, string2) { - var minLen = Math.min(string1.length, string2.length); - for (var i = 0; i < minLen; i++) { - if (string1.charAt(i) !== string2.charAt(i)) { - return i; - } - } - return string1.length === string2.length ? -1 : minLen; - } - - /** - * @param {DOMElement|DOMDocument} container DOM element that may contain - * a React component - * @return {?*} DOM element that may have the reactRoot ID, or null. - */ - function getReactRootElementInContainer(container) { - if (!container) { - return null; - } - - if (container.nodeType === DOC_NODE_TYPE) { - return container.documentElement; - } else { - return container.firstChild; - } - } - - /** - * @param {DOMElement} container DOM element that may contain a React component. - * @return {?string} A "reactRoot" ID, if a React component is rendered. - */ - function getReactRootID(container) { - var rootElement = getReactRootElementInContainer(container); - return rootElement && ReactMount.getID(rootElement); - } - - /** - * Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form - * element can return its control whose name or ID equals ATTR_NAME. All - * DOM nodes support `getAttributeNode` but this can also get called on - * other objects so just return '' if we're given something other than a - * DOM node (such as window). - * - * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node. - * @return {string} ID of the supplied `domNode`. - */ - function getID(node) { - var id = internalGetID(node); - if (id) { - if (nodeCache.hasOwnProperty(id)) { - var cached = nodeCache[id]; - if (cached !== node) { - !!isValid(cached, id) ? false ? invariant(false, 'ReactMount: Two valid but unequal nodes with the same `%s`: %s', ATTR_NAME, id) : invariant(false) : undefined; - - nodeCache[id] = node; - } - } else { - nodeCache[id] = node; - } - } - - return id; - } - - function internalGetID(node) { - // If node is something like a window, document, or text node, none of - // which support attributes or a .getAttribute method, gracefully return - // the empty string, as if the attribute were missing. - return node && node.getAttribute && node.getAttribute(ATTR_NAME) || ''; - } - - /** - * Sets the React-specific ID of the given node. - * - * @param {DOMElement} node The DOM node whose ID will be set. - * @param {string} id The value of the ID attribute. - */ - function setID(node, id) { - var oldID = internalGetID(node); - if (oldID !== id) { - delete nodeCache[oldID]; - } - node.setAttribute(ATTR_NAME, id); - nodeCache[id] = node; - } - - /** - * Finds the node with the supplied React-generated DOM ID. - * - * @param {string} id A React-generated DOM ID. - * @return {DOMElement} DOM node with the suppled `id`. - * @internal - */ - function getNode(id) { - if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { - nodeCache[id] = ReactMount.findReactNodeByID(id); - } - return nodeCache[id]; - } - - /** - * Finds the node with the supplied public React instance. - * - * @param {*} instance A public React instance. - * @return {?DOMElement} DOM node with the suppled `id`. - * @internal - */ - function getNodeFromInstance(instance) { - var id = ReactInstanceMap.get(instance)._rootNodeID; - if (ReactEmptyComponentRegistry.isNullComponentID(id)) { - return null; - } - if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { - nodeCache[id] = ReactMount.findReactNodeByID(id); - } - return nodeCache[id]; - } - - /** - * A node is "valid" if it is contained by a currently mounted container. - * - * This means that the node does not have to be contained by a document in - * order to be considered valid. - * - * @param {?DOMElement} node The candidate DOM node. - * @param {string} id The expected ID of the node. - * @return {boolean} Whether the node is contained by a mounted container. - */ - function isValid(node, id) { - if (node) { - !(internalGetID(node) === id) ? false ? invariant(false, 'ReactMount: Unexpected modification of `%s`', ATTR_NAME) : invariant(false) : undefined; - - var container = ReactMount.findReactContainerForID(id); - if (container && containsNode(container, node)) { - return true; - } - } - - return false; - } - - /** - * Causes the cache to forget about one React-specific ID. - * - * @param {string} id The ID to forget. - */ - function purgeID(id) { - delete nodeCache[id]; - } - - var deepestNodeSoFar = null; - function findDeepestCachedAncestorImpl(ancestorID) { - var ancestor = nodeCache[ancestorID]; - if (ancestor && isValid(ancestor, ancestorID)) { - deepestNodeSoFar = ancestor; - } else { - // This node isn't populated in the cache, so presumably none of its - // descendants are. Break out of the loop. - return false; - } - } - - /** - * Return the deepest cached node whose ID is a prefix of `targetID`. - */ - function findDeepestCachedAncestor(targetID) { - deepestNodeSoFar = null; - ReactInstanceHandles.traverseAncestors(targetID, findDeepestCachedAncestorImpl); - - var foundNode = deepestNodeSoFar; - deepestNodeSoFar = null; - return foundNode; - } - - /** - * Mounts this component and inserts it into the DOM. - * - * @param {ReactComponent} componentInstance The instance to mount. - * @param {string} rootID DOM ID of the root node. - * @param {DOMElement} container DOM element to mount into. - * @param {ReactReconcileTransaction} transaction - * @param {boolean} shouldReuseMarkup If true, do not insert markup - */ - function mountComponentIntoNode(componentInstance, rootID, container, transaction, shouldReuseMarkup, context) { - if (ReactDOMFeatureFlags.useCreateElement) { - context = assign({}, context); - if (container.nodeType === DOC_NODE_TYPE) { - context[ownerDocumentContextKey] = container; - } else { - context[ownerDocumentContextKey] = container.ownerDocument; - } - } - if (false) { - if (context === emptyObject) { - context = {}; - } - var tag = container.nodeName.toLowerCase(); - context[validateDOMNesting.ancestorInfoContextKey] = validateDOMNesting.updatedAncestorInfo(null, tag, null); - } - var markup = ReactReconciler.mountComponent(componentInstance, rootID, transaction, context); - componentInstance._renderedComponent._topLevelWrapper = componentInstance; - ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup, transaction); - } - - /** - * Batched mount. - * - * @param {ReactComponent} componentInstance The instance to mount. - * @param {string} rootID DOM ID of the root node. - * @param {DOMElement} container DOM element to mount into. - * @param {boolean} shouldReuseMarkup If true, do not insert markup - */ - function batchedMountComponentIntoNode(componentInstance, rootID, container, shouldReuseMarkup, context) { - var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( - /* forceHTML */shouldReuseMarkup); - transaction.perform(mountComponentIntoNode, null, componentInstance, rootID, container, transaction, shouldReuseMarkup, context); - ReactUpdates.ReactReconcileTransaction.release(transaction); - } - - /** - * Unmounts a component and removes it from the DOM. - * - * @param {ReactComponent} instance React component instance. - * @param {DOMElement} container DOM element to unmount from. - * @final - * @internal - * @see {ReactMount.unmountComponentAtNode} - */ - function unmountComponentFromNode(instance, container) { - ReactReconciler.unmountComponent(instance); - - if (container.nodeType === DOC_NODE_TYPE) { - container = container.documentElement; - } - - // http://jsperf.com/emptying-a-node - while (container.lastChild) { - container.removeChild(container.lastChild); - } - } - - /** - * True if the supplied DOM node has a direct React-rendered child that is - * not a React root element. Useful for warning in `render`, - * `unmountComponentAtNode`, etc. - * - * @param {?DOMElement} node The candidate DOM node. - * @return {boolean} True if the DOM element contains a direct child that was - * rendered by React but is not a root element. - * @internal - */ - function hasNonRootReactChild(node) { - var reactRootID = getReactRootID(node); - return reactRootID ? reactRootID !== ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false; - } - - /** - * Returns the first (deepest) ancestor of a node which is rendered by this copy - * of React. - */ - function findFirstReactDOMImpl(node) { - // This node might be from another React instance, so we make sure not to - // examine the node cache here - for (; node && node.parentNode !== node; node = node.parentNode) { - if (node.nodeType !== 1) { - // Not a DOMElement, therefore not a React component - continue; - } - var nodeID = internalGetID(node); - if (!nodeID) { - continue; - } - var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID); - - // If containersByReactRootID contains the container we find by crawling up - // the tree, we know that this instance of React rendered the node. - // nb. isValid's strategy (with containsNode) does not work because render - // trees may be nested and we don't want a false positive in that case. - var current = node; - var lastID; - do { - lastID = internalGetID(current); - current = current.parentNode; - if (current == null) { - // The passed-in node has been detached from the container it was - // originally rendered into. - return null; - } - } while (lastID !== reactRootID); - - if (current === containersByReactRootID[reactRootID]) { - return node; - } - } - return null; - } - - /** - * Temporary (?) hack so that we can store all top-level pending updates on - * composites instead of having to worry about different types of components - * here. - */ - var TopLevelWrapper = function () {}; - TopLevelWrapper.prototype.isReactComponent = {}; - if (false) { - TopLevelWrapper.displayName = 'TopLevelWrapper'; - } - TopLevelWrapper.prototype.render = function () { - // this.props is actually a ReactElement - return this.props; - }; - - /** - * Mounting is the process of initializing a React component by creating its - * representative DOM elements and inserting them into a supplied `container`. - * Any prior content inside `container` is destroyed in the process. - * - * ReactMount.render( - * component, - * document.getElementById('container') - * ); - * - *
<-- Supplied `container`. - *
<-- Rendered reactRoot of React - * // ... component. - *
- *
- * - * Inside of `container`, the first element rendered is the "reactRoot". - */ - var ReactMount = { - - TopLevelWrapper: TopLevelWrapper, - - /** Exposed for debugging purposes **/ - _instancesByReactRootID: instancesByReactRootID, - - /** - * This is a hook provided to support rendering React components while - * ensuring that the apparent scroll position of its `container` does not - * change. - * - * @param {DOMElement} container The `container` being rendered into. - * @param {function} renderCallback This must be called once to do the render. - */ - scrollMonitor: function (container, renderCallback) { - renderCallback(); - }, - - /** - * Take a component that's already mounted into the DOM and replace its props - * @param {ReactComponent} prevComponent component instance already in the DOM - * @param {ReactElement} nextElement component instance to render - * @param {DOMElement} container container to render into - * @param {?function} callback function triggered on completion - */ - _updateRootComponent: function (prevComponent, nextElement, container, callback) { - ReactMount.scrollMonitor(container, function () { - ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); - if (callback) { - ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); - } - }); - - if (false) { - // Record the root element in case it later gets transplanted. - rootElementsByReactRootID[getReactRootID(container)] = getReactRootElementInContainer(container); - } - - return prevComponent; - }, - - /** - * Register a component into the instance map and starts scroll value - * monitoring - * @param {ReactComponent} nextComponent component instance to render - * @param {DOMElement} container container to render into - * @return {string} reactRoot ID prefix - */ - _registerComponent: function (nextComponent, container) { - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? false ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : invariant(false) : undefined; - - ReactBrowserEventEmitter.ensureScrollValueMonitoring(); - - var reactRootID = ReactMount.registerContainer(container); - instancesByReactRootID[reactRootID] = nextComponent; - return reactRootID; - }, - - /** - * Render a new component into the DOM. - * @param {ReactElement} nextElement element to render - * @param {DOMElement} container container to render into - * @param {boolean} shouldReuseMarkup if we should skip the markup insertion - * @return {ReactComponent} nextComponent - */ - _renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) { - // Various parts of our code (such as ReactCompositeComponent's - // _renderValidatedComponent) assume that calls to render aren't nested; - // verify that that's the case. - false ? warning(ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : undefined; - - var componentInstance = instantiateReactComponent(nextElement, null); - var reactRootID = ReactMount._registerComponent(componentInstance, container); - - // The initial render is synchronous but any updates that happen during - // rendering, in componentWillMount or componentDidMount, will be batched - // according to the current batching strategy. - - ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, reactRootID, container, shouldReuseMarkup, context); - - if (false) { - // Record the root element in case it later gets transplanted. - rootElementsByReactRootID[reactRootID] = getReactRootElementInContainer(container); - } - - return componentInstance; - }, - - /** - * Renders a React component into the DOM in the supplied `container`. - * - * If the React component was previously rendered into `container`, this will - * perform an update on it and only mutate the DOM as necessary to reflect the - * latest React component. - * - * @param {ReactComponent} parentComponent The conceptual parent of this render tree. - * @param {ReactElement} nextElement Component element to render. - * @param {DOMElement} container DOM element to render into. - * @param {?function} callback function triggered on completion - * @return {ReactComponent} Component instance rendered in `container`. - */ - renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) { - !(parentComponent != null && parentComponent._reactInternalInstance != null) ? false ? invariant(false, 'parentComponent must be a valid React Component') : invariant(false) : undefined; - return ReactMount._renderSubtreeIntoContainer(parentComponent, nextElement, container, callback); - }, - - _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) { - !ReactElement.isValidElement(nextElement) ? false ? invariant(false, 'ReactDOM.render(): Invalid component element.%s', typeof nextElement === 'string' ? ' Instead of passing an element string, make sure to instantiate ' + 'it by passing it to React.createElement.' : typeof nextElement === 'function' ? ' Instead of passing a component class, make sure to instantiate ' + 'it by passing it to React.createElement.' : - // Check if it quacks like an element - nextElement != null && nextElement.props !== undefined ? ' This may be caused by unintentionally loading two independent ' + 'copies of React.' : '') : invariant(false) : undefined; - - false ? warning(!container || !container.tagName || container.tagName.toUpperCase() !== 'BODY', 'render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.') : undefined; - - var nextWrappedElement = new ReactElement(TopLevelWrapper, null, null, null, null, null, nextElement); - - var prevComponent = instancesByReactRootID[getReactRootID(container)]; - - if (prevComponent) { - var prevWrappedElement = prevComponent._currentElement; - var prevElement = prevWrappedElement.props; - if (shouldUpdateReactComponent(prevElement, nextElement)) { - var publicInst = prevComponent._renderedComponent.getPublicInstance(); - var updatedCallback = callback && function () { - callback.call(publicInst); - }; - ReactMount._updateRootComponent(prevComponent, nextWrappedElement, container, updatedCallback); - return publicInst; - } else { - ReactMount.unmountComponentAtNode(container); - } - } - - var reactRootElement = getReactRootElementInContainer(container); - var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); - var containerHasNonRootReactChild = hasNonRootReactChild(container); - - if (false) { - process.env.NODE_ENV !== 'production' ? warning(!containerHasNonRootReactChild, 'render(...): Replacing React-rendered children with a new root ' + 'component. If you intended to update the children of this node, ' + 'you should instead have the existing children update their state ' + 'and render the new components instead of calling ReactDOM.render.') : undefined; - - if (!containerHasReactMarkup || reactRootElement.nextSibling) { - var rootElementSibling = reactRootElement; - while (rootElementSibling) { - if (internalGetID(rootElementSibling)) { - process.env.NODE_ENV !== 'production' ? warning(false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.') : undefined; - break; - } - rootElementSibling = rootElementSibling.nextSibling; - } - } - } - - var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; - var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, parentComponent != null ? parentComponent._reactInternalInstance._processChildContext(parentComponent._reactInternalInstance._context) : emptyObject)._renderedComponent.getPublicInstance(); - if (callback) { - callback.call(component); - } - return component; - }, - - /** - * Renders a React component into the DOM in the supplied `container`. - * - * If the React component was previously rendered into `container`, this will - * perform an update on it and only mutate the DOM as necessary to reflect the - * latest React component. - * - * @param {ReactElement} nextElement Component element to render. - * @param {DOMElement} container DOM element to render into. - * @param {?function} callback function triggered on completion - * @return {ReactComponent} Component instance rendered in `container`. - */ - render: function (nextElement, container, callback) { - return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback); - }, - - /** - * Registers a container node into which React components will be rendered. - * This also creates the "reactRoot" ID that will be assigned to the element - * rendered within. - * - * @param {DOMElement} container DOM element to register as a container. - * @return {string} The "reactRoot" ID of elements rendered within. - */ - registerContainer: function (container) { - var reactRootID = getReactRootID(container); - if (reactRootID) { - // If one exists, make sure it is a valid "reactRoot" ID. - reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID); - } - if (!reactRootID) { - // No valid "reactRoot" ID found, create one. - reactRootID = ReactInstanceHandles.createReactRootID(); - } - containersByReactRootID[reactRootID] = container; - return reactRootID; - }, - - /** - * Unmounts and destroys the React component rendered in the `container`. - * - * @param {DOMElement} container DOM element containing a React component. - * @return {boolean} True if a component was found in and unmounted from - * `container` - */ - unmountComponentAtNode: function (container) { - // Various parts of our code (such as ReactCompositeComponent's - // _renderValidatedComponent) assume that calls to render aren't nested; - // verify that that's the case. (Strictly speaking, unmounting won't cause a - // render but we still don't expect to be in a render call here.) - false ? warning(ReactCurrentOwner.current == null, 'unmountComponentAtNode(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from render ' + 'is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : undefined; - - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? false ? invariant(false, 'unmountComponentAtNode(...): Target container is not a DOM element.') : invariant(false) : undefined; - - var reactRootID = getReactRootID(container); - var component = instancesByReactRootID[reactRootID]; - if (!component) { - // Check if the node being unmounted was rendered by React, but isn't a - // root node. - var containerHasNonRootReactChild = hasNonRootReactChild(container); - - // Check if the container itself is a React root node. - var containerID = internalGetID(container); - var isContainerReactRoot = containerID && containerID === ReactInstanceHandles.getReactRootIDFromNodeID(containerID); - - if (false) { - process.env.NODE_ENV !== 'production' ? warning(!containerHasNonRootReactChild, 'unmountComponentAtNode(): The node you\'re attempting to unmount ' + 'was rendered by React and is not a top-level container. %s', isContainerReactRoot ? 'You may have accidentally passed in a React root node instead ' + 'of its container.' : 'Instead, have the parent component update its state and ' + 'rerender in order to remove this component.') : undefined; - } - - return false; - } - ReactUpdates.batchedUpdates(unmountComponentFromNode, component, container); - delete instancesByReactRootID[reactRootID]; - delete containersByReactRootID[reactRootID]; - if (false) { - delete rootElementsByReactRootID[reactRootID]; - } - return true; - }, - - /** - * Finds the container DOM element that contains React component to which the - * supplied DOM `id` belongs. - * - * @param {string} id The ID of an element rendered by a React component. - * @return {?DOMElement} DOM element that contains the `id`. - */ - findReactContainerForID: function (id) { - var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id); - var container = containersByReactRootID[reactRootID]; - - if (false) { - var rootElement = rootElementsByReactRootID[reactRootID]; - if (rootElement && rootElement.parentNode !== container) { - process.env.NODE_ENV !== 'production' ? warning( - // Call internalGetID here because getID calls isValid which calls - // findReactContainerForID (this function). - internalGetID(rootElement) === reactRootID, 'ReactMount: Root element ID differed from reactRootID.') : undefined; - var containerChild = container.firstChild; - if (containerChild && reactRootID === internalGetID(containerChild)) { - // If the container has a new child with the same ID as the old - // root element, then rootElementsByReactRootID[reactRootID] is - // just stale and needs to be updated. The case that deserves a - // warning is when the container is empty. - rootElementsByReactRootID[reactRootID] = containerChild; - } else { - process.env.NODE_ENV !== 'production' ? warning(false, 'ReactMount: Root element has been removed from its original ' + 'container. New container: %s', rootElement.parentNode) : undefined; - } - } - } - - return container; - }, - - /** - * Finds an element rendered by React with the supplied ID. - * - * @param {string} id ID of a DOM node in the React component. - * @return {DOMElement} Root DOM node of the React component. - */ - findReactNodeByID: function (id) { - var reactRoot = ReactMount.findReactContainerForID(id); - return ReactMount.findComponentRoot(reactRoot, id); - }, - - /** - * Traverses up the ancestors of the supplied node to find a node that is a - * DOM representation of a React component rendered by this copy of React. - * - * @param {*} node - * @return {?DOMEventTarget} - * @internal - */ - getFirstReactDOM: function (node) { - return findFirstReactDOMImpl(node); - }, - - /** - * Finds a node with the supplied `targetID` inside of the supplied - * `ancestorNode`. Exploits the ID naming scheme to perform the search - * quickly. - * - * @param {DOMEventTarget} ancestorNode Search from this root. - * @pararm {string} targetID ID of the DOM representation of the component. - * @return {DOMEventTarget} DOM node with the supplied `targetID`. - * @internal - */ - findComponentRoot: function (ancestorNode, targetID) { - var firstChildren = findComponentRootReusableArray; - var childIndex = 0; - - var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode; - - if (false) { - // This will throw on the next line; give an early warning - process.env.NODE_ENV !== 'production' ? warning(deepestAncestor != null, 'React can\'t find the root component node for data-reactid value ' + '`%s`. If you\'re seeing this message, it probably means that ' + 'you\'ve loaded two copies of React on the page. At this time, only ' + 'a single copy of React can be loaded at a time.', targetID) : undefined; - } - - firstChildren[0] = deepestAncestor.firstChild; - firstChildren.length = 1; - - while (childIndex < firstChildren.length) { - var child = firstChildren[childIndex++]; - var targetChild; - - while (child) { - var childID = ReactMount.getID(child); - if (childID) { - // Even if we find the node we're looking for, we finish looping - // through its siblings to ensure they're cached so that we don't have - // to revisit this node again. Otherwise, we make n^2 calls to getID - // when visiting the many children of a single node in order. - - if (targetID === childID) { - targetChild = child; - } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) { - // If we find a child whose ID is an ancestor of the given ID, - // then we can be sure that we only want to search the subtree - // rooted at this child, so we can throw out the rest of the - // search state. - firstChildren.length = childIndex = 0; - firstChildren.push(child.firstChild); - } - } else { - // If this child had no ID, then there's a chance that it was - // injected automatically by the browser, as when a `` - // element sprouts an extra `` child as a side effect of - // `.innerHTML` parsing. Optimistically continue down this - // branch, but not before examining the other siblings. - firstChildren.push(child.firstChild); - } - - child = child.nextSibling; - } - - if (targetChild) { - // Emptying firstChildren/findComponentRootReusableArray is - // not necessary for correctness, but it helps the GC reclaim - // any nodes that were left at the end of the search. - firstChildren.length = 0; - - return targetChild; - } - } - - firstChildren.length = 0; - - true ? false ? invariant(false, 'findComponentRoot(..., %s): Unable to find 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`.', targetID, ReactMount.getID(ancestorNode)) : invariant(false) : undefined; - }, - - _mountImageIntoNode: function (markup, container, shouldReuseMarkup, transaction) { - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? false ? invariant(false, 'mountComponentIntoNode(...): Target container is not valid.') : invariant(false) : undefined; - - if (shouldReuseMarkup) { - var rootElement = getReactRootElementInContainer(container); - if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { - return; - } else { - var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); - rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); - - var rootMarkup = rootElement.outerHTML; - rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum); - - var normalizedMarkup = markup; - if (false) { - // because rootMarkup is retrieved from the DOM, various normalizations - // will have occurred which will not be present in `markup`. Here, - // insert markup into a

or + + + diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching-01.html b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching-01.html new file mode 100644 index 000000000000..71e030c40994 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching-01.html @@ -0,0 +1,18 @@ + + + + + + + Debugger test page + + + + + + + + + + diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html new file mode 100644 index 000000000000..e3b5452ee3b0 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html @@ -0,0 +1,21 @@ + + + + + + Debugger test page + + + + + + + + + diff --git a/devtools/client/debugger/new/test/mochitest/head.js b/devtools/client/debugger/new/test/mochitest/head.js new file mode 100644 index 000000000000..b8ccbbdd418d --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/head.js @@ -0,0 +1,316 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); +var { Toolbox } = require("devtools/client/framework/toolbox"); +const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/new/test/mochitest/examples/"; + +Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend"); + delete window.resumeTest; +}) + +// Wait until an action of `type` is dispatched. This is different +// then `_afterDispatchDone` because it doesn't wait for async actions +// to be done/errored. Use this if you want to listen for the "start" +// action of an async operation (somewhat rare). +function waitForNextDispatch(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => action.type === type, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +// Wait until an action of `type` is dispatched. If it's part of an +// async operation, wait until the `status` field is "done" or "error" +function _afterDispatchDone(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => { + if (action.type === type) { + return action.status ? + (action.status === "done" || action.status === "error") : + true; + } + }, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +function waitForDispatch(dbg, type, eventRepeat = 1) { + let count = 0; + + return Task.spawn(function* () { + info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)"); + while (count < eventRepeat) { + yield _afterDispatchDone(dbg.store, type); + count++; + info(type + " dispatched " + count + " time(s)"); + } + }); +} + +function waitForThreadEvents(dbg, eventName) { + info("Waiting for thread event '" + eventName + "' to fire."); + const thread = dbg.toolbox.threadClient; + + return new Promise(function(resolve, reject) { + thread.addListener(eventName, function onEvent(eventName, ...args) { + info("Thread event '" + eventName + "' fired."); + thread.removeListener(eventName, onEvent); + resolve.apply(resolve, args); + }); + }); +} + +function waitForState(dbg, predicate) { + return new Promise(resolve => { + const unsubscribe = dbg.store.subscribe(() => { + if (predicate(dbg.store.getState())) { + unsubscribe(); + resolve(); + } + }); + }); +} + +function waitForMs(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +function assertPausedLocation(dbg, source, line) { + const { selectors: { getSelectedSource, getPause }, getState } = dbg; + + // support passing in a partial url and fetching the source + if (typeof source == "string") { + source = findSource(dbg, source); + } + + // check the selected source + is(getSelectedSource(getState()).get("url"), source.url); + + // check the pause location + const location = getPause(getState()).getIn(["frame", "location"]); + is(location.get("sourceId"), source.id); + is(location.get("line"), line); + + // check the debug line + ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"), + "Line is highlighted"); +} + +function isPaused(dbg) { + const { selectors: { getPause }, getState } = dbg; + return !!getPause(getState()); +} + +const waitForPaused = Task.async(function* (dbg) { + // We want to make sure that we get both a real paused event and + // that the state is fully populated. The client may do some more + // work (call other client methods) before populating the state. + return Promise.all([ + yield waitForThreadEvents(dbg, "paused"), + yield waitForState(dbg, state => { + const pause = dbg.selectors.getPause(state); + // Make sure we have the paused state. + if(!pause) { + return false; + } + // Make sure the source text is completely loaded for the + // source we are paused in. + const sourceId = pause.getIn(["frame", "location", "sourceId"]); + const sourceText = dbg.selectors.getSourceText(dbg.getState(), sourceId); + return sourceText && !sourceText.get("loading"); + }) + ]); +}); + +const initDebugger = Task.async(function* (url, ...sources) { + const toolbox = yield openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger"); + const win = toolbox.getPanel("jsdebugger").panelWin; + const store = win.Debugger.store; + const { getSources } = win.Debugger.selectors; + + const dbg = { + actions: win.Debugger.actions, + selectors: win.Debugger.selectors, + getState: store.getState, + store: store, + client: win.Debugger.client, + toolbox: toolbox, + win: win + }; + + if(sources.length) { + // TODO: Extract this out to a utility function + info("Waiting on sources: " + sources.join(", ")); + yield Promise.all(sources.map(url => { + function sourceExists(state) { + return getSources(state).some(s => s.get("url").includes(url)); + } + + if(!sourceExists(store.getState())) { + return waitForState(dbg, sourceExists); + } + })); + } + + return dbg; +}); + +window.resumeTest = undefined; +function pauseTest() { + info("Test paused. Invoke resumeTest to continue."); + return new Promise(resolve => resumeTest = resolve); +} + +// Actions + +function findSource(dbg, url) { + const sources = dbg.selectors.getSources(dbg.getState()); + const source = sources.find(s => s.get("url").includes(url)); + + if(!source) { + throw new Error("Unable to find source: " + url); + } + + return source.toJS(); +} + +function selectSource(dbg, url) { + info("Selecting source: " + url); + const source = findSource(dbg, url); + dbg.actions.selectSource(source.id); + + return waitForDispatch(dbg, "LOAD_SOURCE_TEXT"); +} + +function stepOver(dbg) { + info("Stepping over"); + dbg.actions.stepOver(); + return waitForPaused(dbg); +} + +function stepIn(dbg) { + info("Stepping in"); + dbg.actions.stepIn(); + return waitForPaused(dbg); +} + +function stepOut(dbg) { + info("Stepping out"); + dbg.actions.stepOut(); + return waitForPaused(dbg); +} + +function resume(dbg) { + info("Resuming"); + dbg.actions.resume(); + return waitForThreadEvents(dbg, "resumed"); +} + +function reload(dbg) { + return dbg.client.reload(); +} + +function addBreakpoint(dbg, sourceId, line, col) { + return dbg.actions.addBreakpoint({ sourceId, line, col }); +} + +function togglePauseOnExceptions(dbg, + pauseOnExceptions, ignoreCaughtExceptions) { + + const command = dbg.actions.pauseOnExceptions( + pauseOnExceptions, + ignoreCaughtExceptions + ); + + if (!isPaused(dbg)) { + return waitForThreadEvents(dbg, "resumed"); + } + + return command; +} + +// Helpers +// invoke a global function in the debugged tab +function invokeInTab(fnc) { + return ContentTask.spawn(gBrowser.selectedBrowser, fnc, function* (fnc) { + content.wrappedJSObject[fnc](); // eslint-disable-line mozilla/no-cpows-in-tests, max-len + }); +} + + +function isVisibleWithin(outerEl, innerEl) { + const innerRect = innerEl.getBoundingClientRect(); + const outerRect = outerEl.getBoundingClientRect(); + return innerRect.top > outerRect.top && + innerRect.bottom < outerRect.bottom; +} + +const selectors = { + callStackHeader: ".call-stack-pane ._header", + frame: index => `.frames ul li:nth-child(${index})`, + gutter: i => `.CodeMirror-code *:nth-child(${i}) .CodeMirror-linenumber`, + pauseOnExceptions: ".pause-exceptions", + breakpoint: ".CodeMirror-code > .new-breakpoint", + codeMirror: ".CodeMirror", + resume: ".resume.active", + stepOver: ".stepOver.active", + stepOut: ".stepOut.active", + stepIn: ".stepIn.active" +} + +function getSelector(elementName, ...args) { + let selector = selectors[elementName]; + if (!selector) { + throw new Error(`The selector ${elementName} is not defined`); + } + + if (typeof selector == "function") { + selector = selector(...args) + } + + return selector; +} + +function findElement(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + return dbg.win.document.querySelector(selector); +} + +// click an element in the debugger +function clickElement(dbg, elementName, ...args) { + const selector = getSelector(elementName, ...args); + const doc = dbg.win.document; + return EventUtils.synthesizeMouseAtCenter( + doc.querySelector(selector), + {}, + dbg.win + ); +} + +function toggleCallStack(dbg) { + return findElement(dbg, "callStackHeader").click() +} From 9f838998a8981c346181df3ef5622aa22ae4db19 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Mon, 12 Sep 2016 17:31:51 +0200 Subject: [PATCH 8/8] Bug 1300473 - Properly set recording buttons classes in performance tool. r=gregtatum --- .../components/recording-button.js | 12 +++++-- .../components/recording-controls.js | 34 +++++-------------- .../test/browser_perf-button-states.js | 32 ++++++++--------- .../browser_perf-recording-selected-03.js | 4 +-- devtools/client/themes/common.css | 16 +++++---- devtools/client/themes/performance.css | 9 ++--- 6 files changed, 48 insertions(+), 59 deletions(-) diff --git a/devtools/client/performance/components/recording-button.js b/devtools/client/performance/components/recording-button.js index 3fcf5d5476ce..877fd0e2b8ab 100644 --- a/devtools/client/performance/components/recording-button.js +++ b/devtools/client/performance/components/recording-button.js @@ -13,15 +13,23 @@ module.exports = createClass({ render() { let { onRecordButtonClick, - isRecording + isRecording, + isLocked } = this.props; + let classList = ["devtools-button", "record-button"]; + + if (isRecording) { + classList.push("checked"); + } + return button( { - className: "devtools-toolbarbutton record-button", + className: classList.join(" "), onClick: onRecordButtonClick, "data-standalone": "true", "data-text-only": "true", + disabled: isLocked }, isRecording ? L10N.getStr("recordings.stop") : L10N.getStr("recordings.start") ); diff --git a/devtools/client/performance/components/recording-controls.js b/devtools/client/performance/components/recording-controls.js index 5b83edcd583b..88f788ef387d 100644 --- a/devtools/client/performance/components/recording-controls.js +++ b/devtools/client/performance/components/recording-controls.js @@ -10,24 +10,6 @@ const {div, button} = DOM; module.exports = createClass({ displayName: "Recording Controls", - /** - * Manually handle the "checked" and "locked" attributes, as the DOM element won't - * change by just by changing the checked attribute through React. - */ - componentDidUpdate() { - if (this.props.isRecording) { - this._recordButton.setAttribute("checked", true); - } else { - this._recordButton.removeAttribute("checked"); - } - - if (this.props.isLocked) { - this._recordButton.setAttribute("locked", true); - } else { - this._recordButton.removeAttribute("locked"); - } - }, - render() { let { onClearButtonClick, @@ -37,6 +19,12 @@ module.exports = createClass({ isLocked } = this.props; + let recordButtonClassList = ["devtools-button", "record-button"]; + + if (isRecording) { + recordButtonClassList.push("checked"); + } + return ( div({ className: "devtools-toolbar" }, div({ className: "toolbar-group" }, @@ -48,14 +36,10 @@ module.exports = createClass({ }), button({ id: "main-record-button", - className: "devtools-button record-button", + className: recordButtonClassList.join(" "), + disabled: isLocked, title: L10N.getStr("recordings.start.tooltip"), - onClick: onRecordButtonClick, - checked: isRecording, - ref: (el) => { - this._recordButton = el; - }, - locked: isLocked + onClick: onRecordButtonClick }), button({ id: "import-button", diff --git a/devtools/client/performance/test/browser_perf-button-states.js b/devtools/client/performance/test/browser_perf-button-states.js index 672eceaab2ca..7f7ca1b2a463 100644 --- a/devtools/client/performance/test/browser_perf-button-states.js +++ b/devtools/client/performance/test/browser_perf-button-states.js @@ -16,13 +16,11 @@ add_task(function* () { win: window }); - let { $, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; + let { $, $$, EVENTS, PerformanceController, PerformanceView } = panel.panelWin; + let recordButton = $("#main-record-button"); - ok(!recordButton.hasAttribute("checked"), - "The record button should not be checked yet."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked yet."); + checkRecordButtonsStates(false, false); let uiStartClick = once(PerformanceView, EVENTS.UI_START_RECORDING); let recordingStarted = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, { @@ -37,17 +35,11 @@ add_task(function* () { click(recordButton); yield uiStartClick; - ok(recordButton.hasAttribute("checked"), - "The record button should now be checked."); - ok(recordButton.hasAttribute("locked"), - "The record button should be locked."); + checkRecordButtonsStates(true, true); yield recordingStarted; - ok(recordButton.hasAttribute("checked"), - "The record button should still be checked."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked."); + checkRecordButtonsStates(true, false); yield backendStartReady; yield uiStateRecording; @@ -66,13 +58,19 @@ add_task(function* () { yield uiStopClick; yield recordingStopped; - ok(!recordButton.hasAttribute("checked"), - "The record button should not be checked."); - ok(!recordButton.hasAttribute("locked"), - "The record button should not be locked."); + checkRecordButtonsStates(false, false); yield backendStopReady; yield uiStateRecorded; yield teardownToolboxAndRemoveTab(panel); + + function checkRecordButtonsStates(checked, locked) { + for (let button of $$(".record-button")) { + is(button.classList.contains("checked"), checked, + "The record button checked state should be " + checked); + is(button.disabled, locked, + "The record button locked state should be " + locked); + } + } }); diff --git a/devtools/client/performance/test/browser_perf-recording-selected-03.js b/devtools/client/performance/test/browser_perf-recording-selected-03.js index 59ae76815747..82220cc70414 100644 --- a/devtools/client/performance/test/browser_perf-recording-selected-03.js +++ b/devtools/client/performance/test/browser_perf-recording-selected-03.js @@ -32,9 +32,9 @@ add_task(function* () { RecordingsView.selectedIndex = 0; yield selected; - ok($("#main-record-button").hasAttribute("checked"), + ok($("#main-record-button").classList.contains("checked"), "Button is still checked after selecting another item."); - ok(!$("#main-record-button").hasAttribute("locked"), + ok(!$("#main-record-button").hasAttribute("disabled"), "Button is not locked after selecting another item."); yield stopRecording(panel); diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css index c0cea808d821..e79ca583cdeb 100644 --- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -367,6 +367,7 @@ checkbox:-moz-focusring { } .devtools-button:hover:empty::before, +.devtools-button.checked:empty::before, .devtools-button[checked]:empty::before, .devtools-button[open]:empty::before, .devtools-toolbarbutton:not([label]):hover > image, @@ -378,6 +379,7 @@ checkbox:-moz-focusring { .devtools-button:disabled, .devtools-button[disabled], .devtools-toolbarbutton[disabled] { + pointer-events: none; opacity: 0.5 !important; } @@ -397,29 +399,31 @@ checkbox:-moz-focusring { /* Text-only buttons */ .theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-light .devtools-toolbarbutton[data-text-only] { +.theme-light .devtools-toolbarbutton[data-text-only], +.theme-light .devtools-button:not(:empty) { background-color: var(--toolbar-tab-hover); } .theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]), -.theme-dark .devtools-toolbarbutton[data-text-only] { +.theme-dark .devtools-toolbarbutton[data-text-only], +.theme-dark .devtools-button:not(:empty) { background-color: rgba(0, 0, 0, .2); /* Splitter */ } /* Text-only button states */ -.theme-dark .devtools-button:not(:empty):not([disabled]):hover, +.theme-dark .devtools-button:not(:empty):not(:disabled):hover, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(0, 0, 0, .3); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not([disabled]):hover, +.theme-light .devtools-button:not(:empty):not(:disabled):hover, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover { background: rgba(170, 170, 170, .3); /* Splitters */ } -.theme-dark .devtools-button:not(:empty):not([disabled]):hover:active, +.theme-dark .devtools-button:not(:empty):not(:disabled):hover:active, .theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: rgba(0, 0, 0, .4); /* Splitters */ } -.theme-light .devtools-button:not(:empty):not([disabled]):hover:active, +.theme-light .devtools-button:not(:empty):not(:disabled):hover:active, .theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active { background: var(--toolbar-tab-hover-active); } diff --git a/devtools/client/themes/performance.css b/devtools/client/themes/performance.css index 9349d690941b..142b8fa7e52c 100644 --- a/devtools/client/themes/performance.css +++ b/devtools/client/themes/performance.css @@ -116,17 +116,12 @@ padding: 5px !important; } -.notice-container .record-button[checked], -.notice-container .record-button[checked] { +.notice-container .record-button.checked, +.notice-container .record-button.checked { color: var(--theme-selection-color) !important; background: var(--theme-selection-background) !important; } -.record-button[locked] { - pointer-events: none; - opacity: 0.5; -} - /* Sidebar & recording items */ #recordings-pane {