From 26f84dc0e42507ffa5b6ba5f6b17418ab3fcb9b4 Mon Sep 17 00:00:00 2001 From: yulia Date: Thu, 27 Sep 2018 15:18:08 +0000 Subject: [PATCH 01/25] Bug 1487428 - Use target.getFront in server browser tests; r=ochameau Differential Revision: https://phabricator.services.mozilla.com/D6768 --HG-- extra : moz-landing-system : lando --- .../test/browser_css_autocompletion.js | 3 +- .../browser_editor_autocomplete_events.js | 3 +- ...owser_accessibility_highlighter_infobar.js | 4 +- .../browser/browser_accessibility_node.js | 4 +- .../browser_accessibility_node_events.js | 4 +- .../browser/browser_accessibility_simple.js | 4 +- .../browser/browser_accessibility_walker.js | 4 +- .../browser_animation_emitMutations.js | 4 +- .../browser_animation_getMultipleStates.js | 4 +- .../browser/browser_animation_getPlayers.js | 4 +- .../browser_animation_getProperties.js | 4 +- ...browser_animation_getStateAfterFinished.js | 4 +- .../browser_animation_getSubTreeAnimations.js | 4 +- .../browser/browser_animation_keepFinished.js | 4 +- .../browser_animation_playPauseIframe.js | 4 +- .../browser_animation_playPauseSeveral.js | 4 +- .../browser/browser_animation_playerState.js | 4 +- .../browser_animation_reconstructState.js | 4 +- .../browser_animation_refreshTransitions.js | 4 +- .../browser_animation_setCurrentTime.js | 4 +- .../browser_animation_setPlaybackRate.js | 4 +- .../tests/browser/browser_animation_simple.js | 4 +- .../browser/browser_animation_updatedState.js | 4 +- .../tests/browser/browser_layout_getGrids.js | 4 +- .../tests/browser/browser_layout_simple.js | 4 +- devtools/server/tests/browser/head.js | 63 +++++++++---------- .../mochitest/test_inspector-insert.html | 8 ++- 27 files changed, 84 insertions(+), 85 deletions(-) diff --git a/devtools/client/sourceeditor/test/browser_css_autocompletion.js b/devtools/client/sourceeditor/test/browser_css_autocompletion.js index 7db42cfdc568..dc71f2aebd62 100644 --- a/devtools/client/sourceeditor/test/browser_css_autocompletion.js +++ b/devtools/client/sourceeditor/test/browser_css_autocompletion.js @@ -5,7 +5,6 @@ "use strict"; const CSSCompleter = require("devtools/client/sourceeditor/css-autocompleter"); -const {InspectorFront} = require("devtools/shared/fronts/inspector"); const CSS_URI = "http://mochi.test:8888/browser/devtools/client/sourceeditor" + "/test/css_statemachine_testcases.css"; @@ -86,7 +85,7 @@ add_task(async function test() { async function runTests() { const target = await TargetFactory.forTab(gBrowser.selectedTab); await target.attach(); - inspector = InspectorFront(target.client, target.form); + inspector = target.getFront("inspector"); const walker = await inspector.getWalker(); completer = new CSSCompleter({walker: walker, cssProperties: getClientCssProperties()}); diff --git a/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js b/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js index 1f6f6d230f44..970f0d485f98 100644 --- a/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js +++ b/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js @@ -4,7 +4,6 @@ "use strict"; -const {InspectorFront} = require("devtools/shared/fronts/inspector"); const TEST_URI = "data:text/html;charset=UTF-8," + "
"; @@ -16,7 +15,7 @@ add_task(async function() { async function runTests() { const target = await TargetFactory.forTab(gBrowser.selectedTab); await target.attach(); - const inspector = InspectorFront(target.client, target.form); + const inspector = target.getFront("inspector"); const walker = await inspector.getWalker(); const {ed, win, edWin} = await setup(null, { autocomplete: true, diff --git a/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js index 728d2f36a3d4..e6e4a13cc86e 100644 --- a/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js +++ b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js @@ -10,7 +10,7 @@ const { truncateString } = require("devtools/shared/inspector/utils"); const { MAX_STRING_LENGTH } = require("devtools/server/actors/highlighters/utils/accessibility"); add_task(async function() { - const { client, walker, accessibility } = + const { target, walker, accessibility } = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility_infobar.html"); const a11yWalker = await accessibility.getWalker(); @@ -25,7 +25,7 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_node.js b/devtools/server/tests/browser/browser_accessibility_node.js index 00a260b1c00d..4cc515e0c7f0 100644 --- a/devtools/server/tests/browser/browser_accessibility_node.js +++ b/devtools/server/tests/browser/browser_accessibility_node.js @@ -7,7 +7,7 @@ // Checks for the AccessibleActor add_task(async function() { - const {client, walker, accessibility} = + const {target, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const modifiers = Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; @@ -50,6 +50,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_node_events.js b/devtools/server/tests/browser/browser_accessibility_node_events.js index 8ba83601a934..8fd087618c60 100644 --- a/devtools/server/tests/browser/browser_accessibility_node_events.js +++ b/devtools/server/tests/browser/browser_accessibility_node_events.js @@ -7,7 +7,7 @@ // Checks for the AccessibleActor events add_task(async function() { - const {client, walker, accessibility} = + const {target, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const modifiers = Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; @@ -123,6 +123,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_simple.js b/devtools/server/tests/browser/browser_accessibility_simple.js index 98bbc8382ac4..269ccab16bf2 100644 --- a/devtools/server/tests/browser/browser_accessibility_simple.js +++ b/devtools/server/tests/browser/browser_accessibility_simple.js @@ -16,7 +16,7 @@ function checkAccessibilityState(accessibility, expected) { // Simple checks for the AccessibilityActor and AccessibleWalkerActor add_task(async function() { - const { walker: domWalker, client, accessibility} = await initAccessibilityFrontForUrl( + const { walker: domWalker, target, accessibility} = await initAccessibilityFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(accessibility, "The AccessibilityFront was created"); @@ -63,6 +63,6 @@ add_task(async function() { checkAccessibilityState(accessibility, { enabled: false, canBeDisabled: true, canBeEnabled: true }); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_walker.js b/devtools/server/tests/browser/browser_accessibility_walker.js index 2258de35e4e1..e4f34b2c3810 100644 --- a/devtools/server/tests/browser/browser_accessibility_walker.js +++ b/devtools/server/tests/browser/browser_accessibility_walker.js @@ -7,7 +7,7 @@ // Checks for the AccessibleWalkerActor add_task(async function() { - const {client, walker, accessibility} = + const {target, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const a11yWalker = await accessibility.getWalker(); @@ -127,6 +127,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_emitMutations.js b/devtools/server/tests/browser/browser_animation_emitMutations.js index 20c3d8b3f5a3..170e1c895599 100644 --- a/devtools/server/tests/browser/browser_animation_emitMutations.js +++ b/devtools/server/tests/browser/browser_animation_emitMutations.js @@ -8,7 +8,7 @@ // node after getAnimationPlayersForNode was called on that node. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non-animated node"); @@ -57,6 +57,6 @@ add_task(async function() { ok(changes[1].player === p1 || changes[1].player === p2, "The second removed player was one of the previously added players"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getMultipleStates.js b/devtools/server/tests/browser/browser_animation_getMultipleStates.js index 93c22de47f47..eaf9ce43a05f 100644 --- a/devtools/server/tests/browser/browser_animation_getMultipleStates.js +++ b/devtools/server/tests/browser/browser_animation_getMultipleStates.js @@ -8,12 +8,12 @@ // multiple animations. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasAnInitialState(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getPlayers.js b/devtools/server/tests/browser/browser_animation_getPlayers.js index f3389c9d715d..a23659f167b5 100644 --- a/devtools/server/tests/browser/browser_animation_getPlayers.js +++ b/devtools/server/tests/browser/browser_animation_getPlayers.js @@ -7,12 +7,12 @@ // Check the output of getAnimationPlayersForNode add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await theRightNumberOfPlayersIsReturned(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getProperties.js b/devtools/server/tests/browser/browser_animation_getProperties.js index 8ac0561c6406..6043ca787def 100644 --- a/devtools/server/tests/browser/browser_animation_getProperties.js +++ b/devtools/server/tests/browser/browser_animation_getProperties.js @@ -10,7 +10,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { - const {client, walker, animations} = await initAnimationsFrontForUrl(URL); + const {target, walker, animations} = await initAnimationsFrontForUrl(URL); info("Get the test node and its animation front"); const node = await walker.querySelector(walker.rootNode, ".simple-animation"); @@ -31,6 +31,6 @@ add_task(async function() { // purpose. This object comes straight out of the web animations API // unmodified. - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js index 8c79dab8345a..fe4eccdd06b6 100644 --- a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js +++ b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js @@ -12,7 +12,7 @@ // information. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non animated node"); @@ -51,6 +51,6 @@ add_task(async function() { is(players[1].state.iterationCount, 100, "The iterationCount of the second animation is correct"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js index 9760cef4934b..04b4110bbf3b 100644 --- a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js +++ b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js @@ -12,7 +12,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { info("Creating a test document with 2 iframes containing animated nodes"); - const {client, walker, animations} = await initAnimationsFrontForUrl( + const {target, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8," + ""); @@ -33,6 +33,6 @@ add_task(async function() { // at least have the infinitely running animations. ok(players.length >= 4, "All subtree animations were retrieved"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_keepFinished.js b/devtools/server/tests/browser/browser_animation_keepFinished.js index af19dbd39cf0..825bb18e0700 100644 --- a/devtools/server/tests/browser/browser_animation_keepFinished.js +++ b/devtools/server/tests/browser/browser_animation_keepFinished.js @@ -11,7 +11,7 @@ // AnimationPlayerActor. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non-animated node"); @@ -44,7 +44,7 @@ add_task(async function() { animations.off("mutations", onMutations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playPauseIframe.js b/devtools/server/tests/browser/browser_animation_playPauseIframe.js index 621f53c4bacb..03af6327db66 100644 --- a/devtools/server/tests/browser/browser_animation_playPauseIframe.js +++ b/devtools/server/tests/browser/browser_animation_playPauseIframe.js @@ -12,7 +12,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { info("Creating a test document with 2 iframes containing animated nodes"); - const {client, walker, animations} = await initAnimationsFrontForUrl( + const {target, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8," + "" + ""); @@ -29,7 +29,7 @@ add_task(async function() { await toggleAndCheckStates(animations, nodeInFrame1, "running"); await toggleAndCheckStates(animations, nodeInFrame2, "running"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js index 773f40807fab..839b5511240a 100644 --- a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js +++ b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js @@ -15,7 +15,7 @@ const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations", ".delayed-animation"]; add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Pause all animations in the test document"); @@ -24,7 +24,7 @@ add_task(async function() { info("Play all animations in the test document"); await toggleAndCheckStates(walker, animations, ALL_ANIMATED_NODES, "running"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playerState.js b/devtools/server/tests/browser/browser_animation_playerState.js index 94b9a41a8a0c..f8e802011397 100644 --- a/devtools/server/tests/browser/browser_animation_playerState.js +++ b/devtools/server/tests/browser/browser_animation_playerState.js @@ -7,13 +7,13 @@ // Check the animation player's initial state add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasAnInitialState(walker, animations); await playerStateIsCorrect(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_reconstructState.js b/devtools/server/tests/browser/browser_animation_reconstructState.js index 617cb49f4c5c..18de3bddb544 100644 --- a/devtools/server/tests/browser/browser_animation_reconstructState.js +++ b/devtools/server/tests/browser/browser_animation_reconstructState.js @@ -8,12 +8,12 @@ // state that change, the front reconstructs the whole state everytime. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasCompleteStateAtAllTimes(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_refreshTransitions.js b/devtools/server/tests/browser/browser_animation_refreshTransitions.js index b72252e9838a..0bacb6766a01 100644 --- a/devtools/server/tests/browser/browser_animation_refreshTransitions.js +++ b/devtools/server/tests/browser/browser_animation_refreshTransitions.js @@ -9,7 +9,7 @@ // AnimationPlayerFront should be sent, and the old one should be removed. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve the test node"); @@ -49,7 +49,7 @@ add_task(async function() { is(reportedMutations.filter(m => m.type === "added").length, 2, "2 'added' events were sent (for the new transitions)"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_setCurrentTime.js b/devtools/server/tests/browser/browser_animation_setCurrentTime.js index bd3240e16ae5..352dbff882ca 100644 --- a/devtools/server/tests/browser/browser_animation_setCurrentTime.js +++ b/devtools/server/tests/browser/browser_animation_setCurrentTime.js @@ -7,12 +7,12 @@ // Check that the AnimationsActor allows changing many players' currentTimes at once. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await testSetCurrentTimes(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js index 4fcf0027f4a9..9b4aa374d05f 100644 --- a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js +++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js @@ -8,7 +8,7 @@ // can have their rates changed at the same time. add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve an animated node"); @@ -44,6 +44,6 @@ add_task(async function() { is(animPlayerState.playbackRate, .5, "The playbackRate was updated"); } - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_simple.js b/devtools/server/tests/browser/browser_animation_simple.js index 99039e888405..9cbd172ffae3 100644 --- a/devtools/server/tests/browser/browser_animation_simple.js +++ b/devtools/server/tests/browser/browser_animation_simple.js @@ -7,7 +7,7 @@ // Simple checks for the AnimationsActor add_task(async function() { - const {client, walker, animations} = await initAnimationsFrontForUrl( + const {target, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(animations, "The AnimationsFront was created"); @@ -32,6 +32,6 @@ add_task(async function() { ok(Array.isArray(players), "An array of players was returned"); is(players.length, 0, "0 players have been returned for the invalid node"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_updatedState.js b/devtools/server/tests/browser/browser_animation_updatedState.js index dcb5f7d40a80..5839ce178eec 100644 --- a/devtools/server/tests/browser/browser_animation_updatedState.js +++ b/devtools/server/tests/browser/browser_animation_updatedState.js @@ -8,12 +8,12 @@ // Check the animation player's updated state add_task(async function() { - const {client, walker, animations} = + const {target, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playStateIsUpdatedDynamically(walker, animations); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_layout_getGrids.js b/devtools/server/tests/browser/browser_layout_getGrids.js index a3fad397b958..e2e33b4bbe94 100644 --- a/devtools/server/tests/browser/browser_layout_getGrids.js +++ b/devtools/server/tests/browser/browser_layout_getGrids.js @@ -108,7 +108,7 @@ const GRID_FRAGMENT_DATA = { }; add_task(async function() { - const { client, walker, layout } = + const { target, walker, layout } = await initLayoutFrontForUrl(MAIN_DOMAIN + "grid.html"); const grids = await layout.getGrids(walker.rootNode); const grid = grids[0]; @@ -129,6 +129,6 @@ add_task(async function() { ok(false, "Did not get grid container node front."); } - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_layout_simple.js b/devtools/server/tests/browser/browser_layout_simple.js index 0f24bebe1bec..bbf1b55aa42c 100644 --- a/devtools/server/tests/browser/browser_layout_simple.js +++ b/devtools/server/tests/browser/browser_layout_simple.js @@ -7,7 +7,7 @@ // Simple checks for the LayoutActor and GridActor add_task(async function() { - const {client, walker, layout} = await initLayoutFrontForUrl( + const {target, walker, layout} = await initLayoutFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(layout, "The LayoutFront was created"); @@ -26,6 +26,6 @@ add_task(async function() { ok(Array.isArray(grids), "An array of grids was returned"); is(grids.length, 0, "0 grids have been returned for the invalid node"); - await client.close(); + await target.destroy(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js index 5495cc8db84b..353d53436690 100644 --- a/devtools/server/tests/browser/head.js +++ b/devtools/server/tests/browser/head.js @@ -41,43 +41,50 @@ var addTab = async function(url) { const tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - info(`Tab added and URL ${url} loaded`); + info(`Tab added a URL ${url} loaded`); return tab.linkedBrowser; }; +// does almost the same thing as addTab, but directly returns an object +async function addTabTarget(url) { + info(`Adding a new tab with URL: ${url}`); + const tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + info(`Tab added a URL ${url} loaded`); + return getTargetForTab(tab); +} + +async function getTargetForTab(tab) { + const target = await TargetFactory.forTab(tab); + info("Attaching to the active tab."); + await target.attach(); + return target; +} + async function initAnimationsFrontForUrl(url) { - const {AnimationsFront} = require("devtools/shared/fronts/animation"); + const { inspector, walker, target } = await initInspectorFront(url); + const animations = target.getFront("animations"); - const { inspector, walker, client, form } = await initInspectorFront(url); - const animations = AnimationsFront(client, form); - - return {inspector, walker, animations, client}; + return {inspector, walker, animations, target}; } async function initLayoutFrontForUrl(url) { - const {inspector, walker, client} = await initInspectorFront(url); + const {inspector, walker, target} = await initInspectorFront(url); const layout = await walker.getLayoutInspector(); - return {inspector, walker, layout, client}; + return {inspector, walker, layout, target}; } async function initAccessibilityFrontForUrl(url) { - const {AccessibilityFront} = require("devtools/shared/fronts/accessibility"); - const {InspectorFront} = require("devtools/shared/fronts/inspector"); - - await addTab(url); - - initDebuggerServer(); - const client = new DebuggerClient(DebuggerServer.connectPipe()); - const form = await connectDebuggerClient(client); - const inspector = InspectorFront(client, form); + const target = await addTabTarget(url); + const inspector = target.getFront("inspector"); const walker = await inspector.getWalker(); - const accessibility = AccessibilityFront(client, form); + const accessibility = target.getFront("accessibility"); await accessibility.bootstrap(); - return {inspector, walker, accessibility, client}; + return {inspector, walker, accessibility, target}; } function initDebuggerServer() { @@ -93,28 +100,20 @@ function initDebuggerServer() { } async function initPerfFront() { - const {PerfFront} = require("devtools/shared/fronts/perf"); - initDebuggerServer(); const client = new DebuggerClient(DebuggerServer.connectPipe()); await waitUntilClientConnected(client); - const rootForm = await getRootForm(client); - const front = PerfFront(client, rootForm); + const front = await client.mainRoot.getFront("perf"); return {front, client}; } async function initInspectorFront(url) { - const { InspectorFront } = require("devtools/shared/fronts/inspector"); - await addTab(url); - - initDebuggerServer(); - const client = new DebuggerClient(DebuggerServer.connectPipe()); - const form = await connectDebuggerClient(client); - const inspector = InspectorFront(client, form); + const target = await addTabTarget(url); + const inspector = target.getFront("inspector"); const walker = await inspector.getWalker(); - return {inspector, walker, client, form}; + return {inspector, walker, target}; } /** @@ -249,7 +248,7 @@ function waitForMarkerType(front, types, predicate, } const markers = unpackFun(name, data); - info("Got markers: " + JSON.stringify(markers, null, 2)); + info("Got markers"); filteredMarkers = filteredMarkers.concat( markers.filter(m => types.includes(m.name))); diff --git a/devtools/server/tests/mochitest/test_inspector-insert.html b/devtools/server/tests/mochitest/test_inspector-insert.html index 69a63c0fb852..505713b70d79 100644 --- a/devtools/server/tests/mochitest/test_inspector-insert.html +++ b/devtools/server/tests/mochitest/test_inspector-insert.html @@ -25,10 +25,12 @@ let gInspectee = null; addTest(function setup() { const url = document.getElementById("inspectorContent").href; - attachURL(url, function(err, client, tab, doc) { + attachURL(url, async function(err, client, tab, doc) { gInspectee = doc; - const {InspectorFront} = require("devtools/shared/fronts/inspector"); - const inspectorFront = InspectorFront(client, tab); + const {TargetFactory} = require("devtools/client/framework/target"); + const target = await TargetFactory.forTab(tab); + await target.attach(); + const inspectorFront = target.getFront("inspector"); promiseDone(inspectorFront.getWalker().then(walker => { ok(walker, "getWalker() should return an actor."); gWalker = walker; From 9b4e589152aee0cd34af3f9058ad014c6b2211cc Mon Sep 17 00:00:00 2001 From: Chris Manchester Date: Wed, 26 Sep 2018 22:18:25 +0000 Subject: [PATCH 02/25] Bug 1490147 - Require rustc 1.29. r=ted,firefox-build-system-reviewers Differential Revision: https://phabricator.services.mozilla.com/D6998 --HG-- extra : moz-landing-system : lando --- build/moz.configure/rust.configure | 2 +- taskcluster/ci/build/linux.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure index 9ec940c8f6ab..a68567a91431 100644 --- a/build/moz.configure/rust.configure +++ b/build/moz.configure/rust.configure @@ -72,7 +72,7 @@ def rust_compiler(rustc_info, cargo_info): You can install rust by running './mach bootstrap' or by directly running the installer from https://rustup.rs/ ''')) - rustc_min_version = Version('1.28.0') + rustc_min_version = Version('1.29.0') cargo_min_version = rustc_min_version version = rustc_info.version diff --git a/taskcluster/ci/build/linux.yml b/taskcluster/ci/build/linux.yml index b6cdf500358a..db5ab4049dd7 100644 --- a/taskcluster/ci/build/linux.yml +++ b/taskcluster/ci/build/linux.yml @@ -290,7 +290,7 @@ linux64-base-toolchains/opt: toolchains: - linux64-clang-3.9 - linux64-gcc-6 - - linux64-rust-1.28 + - linux64-rust-1.29 - linux64-cbindgen - linux64-sccache - linux64-node @@ -323,7 +323,7 @@ linux64-base-toolchains/debug: toolchains: - linux64-clang-3.9 - linux64-gcc-6 - - linux64-rust-1.28 + - linux64-rust-1.29 - linux64-cbindgen - linux64-sccache - linux64-node From 877213ca74c8d133b1a8f3c8261e0e7eb7e2e2a7 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Thu, 27 Sep 2018 18:41:42 +0000 Subject: [PATCH 03/25] Bug 1493791 - Fix trivial calls to do_QueryInterface in XPCOM unit tests r=froydnj In TestEventTargetQI.cpp, nsIThreadPool is a subclass of nsIEventTarget, so we cannot QI from the former to the latter once trivial QIs are banned. However, we still want to check if a QI to nsIEventTarget does something, so I added an intermediate cast up to nsISupports, and then QI from there. I wrapped that all up in a templated function, because the code is a bit ugly, and used it everywhere for uniformity, even though it is not always needed. I fixed TestCOMPtr.cpp in the same way, by casting up to nsISupports. Differential Revision: https://phabricator.services.mozilla.com/D6855 --HG-- extra : moz-landing-system : lando --- xpcom/tests/gtest/TestCOMPtr.cpp | 26 +++++++------- xpcom/tests/gtest/TestEventTargetQI.cpp | 45 ++++++++++++------------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp index 83de2725c822..ca742d377718 100644 --- a/xpcom/tests/gtest/TestCOMPtr.cpp +++ b/xpcom/tests/gtest/TestCOMPtr.cpp @@ -113,14 +113,16 @@ CreateIFoo( void** result ) void set_a_IFoo( nsCOMPtr* result ) { - nsCOMPtr foop( do_QueryInterface(new IFoo) ); + // Various places in this file do a static_cast to nsISupports* in order to + // make the QI non-trivial, to avoid hitting a static assert. + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); *result = foop; } nsCOMPtr return_a_IFoo() { - nsCOMPtr foop( do_QueryInterface(new IFoo) ); + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); return foop; } @@ -242,7 +244,7 @@ TEST(COMPtr, Bloat_Smart) ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(barP); - nsCOMPtr fooP( do_QueryInterface(barP, &rv) ); + nsCOMPtr fooP(do_QueryInterface(static_cast(barP), &rv)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(fooP); } @@ -254,12 +256,12 @@ TEST(COMPtr, AddRefAndRelease) IBar::total_destructions_ = 0; { - nsCOMPtr foop( do_QueryInterface(new IFoo) ); + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); ASSERT_EQ(foop->refcount_, (unsigned int)1); ASSERT_EQ(IFoo::total_constructions_, 1); ASSERT_EQ(IFoo::total_destructions_, 0); - foop = do_QueryInterface(new IFoo); + foop = do_QueryInterface(static_cast(new IFoo)); ASSERT_EQ(foop->refcount_, (unsigned int)1); ASSERT_EQ(IFoo::total_constructions_, 2); ASSERT_EQ(IFoo::total_destructions_, 1); @@ -288,7 +290,7 @@ TEST(COMPtr, AddRefAndRelease) ASSERT_EQ(IFoo::total_destructions_, 2); { - nsCOMPtr foop( do_QueryInterface(new IBar) ); + nsCOMPtr foop(do_QueryInterface(static_cast(new IBar))); mozilla::Unused << foop; } @@ -301,8 +303,8 @@ void Comparison() IFoo::total_destructions_ = 0; { - nsCOMPtr foo1p( do_QueryInterface(new IFoo) ); - nsCOMPtr foo2p( do_QueryInterface(new IFoo) ); + nsCOMPtr foo1p(do_QueryInterface(static_cast(new IFoo))); + nsCOMPtr foo2p(do_QueryInterface(static_cast(new IFoo))); ASSERT_EQ(IFoo::total_constructions_, 2); @@ -394,7 +396,7 @@ TEST(COMPtr, AssignmentHelpers) ASSERT_EQ(IFoo::total_destructions_, 4); { - nsCOMPtr fooP( do_QueryInterface(new IFoo) ); + nsCOMPtr fooP(do_QueryInterface(static_cast(new IFoo))); ASSERT_TRUE(fooP); ASSERT_EQ(IFoo::total_constructions_, 5); @@ -419,7 +421,7 @@ TEST(COMPtr, QueryInterface) { nsCOMPtr fooP; ASSERT_FALSE(fooP); - fooP = do_QueryInterface(new IFoo); + fooP = do_QueryInterface(static_cast(new IFoo)); ASSERT_TRUE(fooP); ASSERT_EQ(IFoo::total_queries_, 1); @@ -432,12 +434,12 @@ TEST(COMPtr, QueryInterface) } { - nsCOMPtr barP( do_QueryInterface(new IBar) ); + nsCOMPtr barP(do_QueryInterface(static_cast(new IBar))); ASSERT_EQ(IBar::total_queries_, 1); // Test that |QueryInterface| is called when assigning a smart-pointer of // a different type. - nsCOMPtr fooP( do_QueryInterface(barP) ); + nsCOMPtr fooP(do_QueryInterface(static_cast(barP))); ASSERT_EQ(IBar::total_queries_, 2); ASSERT_EQ(IFoo::total_queries_, 1); ASSERT_TRUE(fooP); diff --git a/xpcom/tests/gtest/TestEventTargetQI.cpp b/xpcom/tests/gtest/TestEventTargetQI.cpp index 67e9a75a24b6..f46e2423fdd3 100644 --- a/xpcom/tests/gtest/TestEventTargetQI.cpp +++ b/xpcom/tests/gtest/TestEventTargetQI.cpp @@ -18,15 +18,22 @@ using namespace mozilla; +// Cast the pointer to nsISupports* before doing the QI in order to avoid +// a static assert intended to prevent trivial QIs. +template +bool TestQITo(SourcePtr& aPtr1) +{ + nsCOMPtr aPtr2 = do_QueryInterface(static_cast(aPtr1.get())); + return (bool) aPtr2; +} + TEST(TestEventTargetQI, ThreadPool) { nsCOMPtr thing = new nsThreadPool(); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_FALSE(serial); + EXPECT_FALSE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); thing->Shutdown(); } @@ -36,11 +43,9 @@ TEST(TestEventTargetQI, SharedThreadPool) nsCOMPtr thing = SharedThreadPool::Get(NS_LITERAL_CSTRING("TestPool"), 1); EXPECT_TRUE(thing); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_FALSE(serial); + EXPECT_FALSE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); } TEST(TestEventTargetQI, Thread) @@ -48,11 +53,9 @@ TEST(TestEventTargetQI, Thread) nsCOMPtr thing = do_GetCurrentThread(); EXPECT_TRUE(thing); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_TRUE(serial); + EXPECT_TRUE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); } TEST(TestEventTargetQI, ThrottledEventQueue) @@ -61,11 +64,9 @@ TEST(TestEventTargetQI, ThrottledEventQueue) RefPtr thing = ThrottledEventQueue::Create(thread); EXPECT_TRUE(thing); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_TRUE(serial); + EXPECT_TRUE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); } TEST(TestEventTargetQI, LazyIdleThread) @@ -73,11 +74,9 @@ TEST(TestEventTargetQI, LazyIdleThread) nsCOMPtr thing = new LazyIdleThread(0, NS_LITERAL_CSTRING("TestThread")); EXPECT_TRUE(thing); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_TRUE(serial); + EXPECT_TRUE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); thing->Shutdown(); } @@ -87,9 +86,7 @@ TEST(TestEventTargetQI, SchedulerGroup) nsCOMPtr thing = SystemGroup::EventTargetFor(TaskCategory::Other); EXPECT_TRUE(thing); - nsCOMPtr serial = do_QueryInterface(thing); - EXPECT_TRUE(serial); + EXPECT_TRUE(TestQITo(thing)); - nsCOMPtr target = do_QueryInterface(thing); - EXPECT_TRUE(target); + EXPECT_TRUE(TestQITo(thing)); } From 20456aeee221690ec2c702644fbd6fd870121c8d Mon Sep 17 00:00:00 2001 From: Iain Ireland Date: Thu, 27 Sep 2018 19:18:07 +0000 Subject: [PATCH 04/25] Bug 1492574 - Avoid leaking OOM exception flag from GetDynamicName r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D6404 --HG-- extra : moz-landing-system : lando --- js/src/jit-test/tests/ion/bug1492574.js | 22 ++++++++++++++++++++++ js/src/jit/VMFunctions.cpp | 1 + 2 files changed, 23 insertions(+) create mode 100644 js/src/jit-test/tests/ion/bug1492574.js diff --git a/js/src/jit-test/tests/ion/bug1492574.js b/js/src/jit-test/tests/ion/bug1492574.js new file mode 100644 index 000000000000..9f2efec05062 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1492574.js @@ -0,0 +1,22 @@ +// |jit-test| --fuzzing-safe + +if (!('oomTest' in this)) { + quit(); +} + +function foo() {} +function foooooooooooooooooooooooooooooooo() {} +function fn(s) { + var o = {a:1} + eval(("f" + s) + "()"); + if (!('a' in o)) { + print("unreachable"); + } +} +for (var i = 0; i < 1100; i++) { + fn("oo"); +} +oomTest(new Function(` + let a = newRope("oooooooooooooooo","oooooooooooooooo"); + fn(a); +`)) diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index ab7c5e23a706..b392ba896787 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -726,6 +726,7 @@ GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) atom = AtomizeString(cx, str); if (!atom) { vp->setUndefined(); + cx->recoverFromOutOfMemory(); return; } } From e1f5fb98a51fc4c24c366e944b7542bcdcd46b7b Mon Sep 17 00:00:00 2001 From: Iain Ireland Date: Thu, 27 Sep 2018 19:13:52 +0000 Subject: [PATCH 05/25] Bug 1492574: Fix OOM handling in NewRope testing function r=tcampbell Depends on D6404 Differential Revision: https://phabricator.services.mozilla.com/D7122 --HG-- extra : moz-landing-system : lando --- js/src/builtin/TestingFunctions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index dc43dd2e2423..8001800ac0ea 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1696,6 +1696,7 @@ NewRope(JSContext* cx, unsigned argc, Value* vp) Rooted str(cx, JSRope::new_(cx, left, right, length, heap)); if (!str) { + JS_ReportOutOfMemory(cx); return false; } From 0e0311a5845c93a49e68f175758d15065faaca81 Mon Sep 17 00:00:00 2001 From: Iain Ireland Date: Thu, 27 Sep 2018 19:17:06 +0000 Subject: [PATCH 06/25] Bug 1492574: Rewrite GetDynamicName to return false if lookup can't be completed r=tcampbell Depends on D7122 Differential Revision: https://phabricator.services.mozilla.com/D7123 --HG-- extra : moz-landing-system : lando --- js/src/jit-test/tests/ion/bug1492574.js | 2 -- js/src/jit/CodeGenerator.cpp | 6 ++---- js/src/jit/VMFunctions.cpp | 23 +++++++++-------------- js/src/jit/VMFunctions.h | 2 +- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/js/src/jit-test/tests/ion/bug1492574.js b/js/src/jit-test/tests/ion/bug1492574.js index 9f2efec05062..2a55ef1fb037 100644 --- a/js/src/jit-test/tests/ion/bug1492574.js +++ b/js/src/jit-test/tests/ion/bug1492574.js @@ -1,5 +1,3 @@ -// |jit-test| --fuzzing-safe - if (!('oomTest' in this)) { quit(); } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1ad7d4c42b77..9e27e51d6a9d 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5315,16 +5315,14 @@ CodeGenerator::visitGetDynamicName(LGetDynamicName* lir) masm.passABIArg(envChain); masm.passABIArg(name); masm.passABIArg(temp2); - masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetDynamicName)); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetDynamicNamePure)); const ValueOperand out = ToOutValue(lir); masm.loadValue(Address(masm.getStackPointer(), 0), out); masm.adjustStack(sizeof(Value)); - Label undefined; - masm.branchTestUndefined(Assembler::Equal, out, &undefined); - bailoutFrom(&undefined, lir->snapshot()); + bailoutIfFalseBool(ReturnReg, lir->snapshot()); } typedef bool (*DirectEvalSFn)(JSContext*, HandleObject, HandleScript, HandleValue, diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index b392ba896787..f247fafc745e 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -710,12 +710,12 @@ CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHa return true; } -void -GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) +bool +GetDynamicNamePure(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) { - // Lookup a string on the env chain, returning either the value found or - // undefined through rval. This function is infallible, and cannot GC or - // invalidate. + // Lookup a string on the env chain, returning the value found through rval. + // This function is infallible, and cannot GC or invalidate. + // Returns false if the lookup could not be completed without GC. AutoUnsafeCallWithABI unsafe; @@ -725,27 +725,22 @@ GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) } else { atom = AtomizeString(cx, str); if (!atom) { - vp->setUndefined(); cx->recoverFromOutOfMemory(); - return; + return false; } } if (!frontend::IsIdentifier(atom) || frontend::IsKeyword(atom)) { - vp->setUndefined(); - return; + return false; } PropertyResult prop; JSObject* scope = nullptr; JSObject* pobj = nullptr; if (LookupNameNoGC(cx, atom->asPropertyName(), envChain, &scope, &pobj, &prop)) { - if (FetchNameNoGC(pobj, prop, MutableHandleValue::fromMarkedLocation(vp))) { - return; - } + return FetchNameNoGC(pobj, prop, MutableHandleValue::fromMarkedLocation(vp)); } - - vp->setUndefined(); + return false; } void diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 7495188cdb9f..eae43f867cef 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -751,7 +751,7 @@ GetIntrinsicValue(JSContext* cx, HandlePropertyName name, MutableHandleValue rva MOZ_MUST_USE bool CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval); -void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp); +bool GetDynamicNamePure(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp); void PostWriteBarrier(JSRuntime* rt, js::gc::Cell* cell); void PostGlobalWriteBarrier(JSRuntime* rt, GlobalObject* obj); From 4de01379de4bf87d115f2342d468f3da526f3ee9 Mon Sep 17 00:00:00 2001 From: Iain Ireland Date: Thu, 27 Sep 2018 19:13:48 +0000 Subject: [PATCH 07/25] Bug 1492574: Remove unnecessary MutableHandle r=tcampbell Depends on D7123 Differential Revision: https://phabricator.services.mozilla.com/D7124 --HG-- extra : moz-landing-system : lando --- js/src/jit/VMFunctions.cpp | 2 +- js/src/vm/Interpreter-inl.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index f247fafc745e..7d2e8d33ba4b 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -738,7 +738,7 @@ GetDynamicNamePure(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) JSObject* scope = nullptr; JSObject* pobj = nullptr; if (LookupNameNoGC(cx, atom->asPropertyName(), envChain, &scope, &pobj, &prop)) { - return FetchNameNoGC(pobj, prop, MutableHandleValue::fromMarkedLocation(vp)); + return FetchNameNoGC(pobj, prop, vp); } return false; } diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index d970af4d99e9..b4c20d2a88fb 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -228,7 +228,7 @@ FetchName(JSContext* cx, HandleObject receiver, HandleObject holder, HandlePrope } inline bool -FetchNameNoGC(JSObject* pobj, PropertyResult prop, MutableHandleValue vp) +FetchNameNoGC(JSObject* pobj, PropertyResult prop, Value* vp) { if (!prop || !pobj->isNative()) { return false; @@ -239,8 +239,8 @@ FetchNameNoGC(JSObject* pobj, PropertyResult prop, MutableHandleValue vp) return false; } - vp.set(pobj->as().getSlot(shape->slot())); - return !IsUninitializedLexical(vp); + *vp = pobj->as().getSlot(shape->slot()); + return !IsUninitializedLexical(*vp); } template @@ -253,7 +253,7 @@ GetEnvironmentName(JSContext* cx, HandleObject envChain, HandlePropertyName name JSObject* obj = nullptr; JSObject* pobj = nullptr; if (LookupNameNoGC(cx, name, envChain, &obj, &pobj, &prop)) { - if (FetchNameNoGC(pobj, prop, vp)) { + if (FetchNameNoGC(pobj, prop, vp.address())) { return true; } } From d8c0080a1094cf59a2e630d70a87eab843218380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 27 Sep 2018 19:00:33 +0000 Subject: [PATCH 08/25] Bug 1493222 - Expose InspectorUtils to fuzzers. r=bzbarsky I can be more granular if we want, by adding more ChromeOnly annotations for the functions that we don't want to expose. Differential Revision: https://phabricator.services.mozilla.com/D6530 --HG-- extra : moz-landing-system : lando --- dom/base/nsContentUtils.cpp | 8 ++++++++ dom/base/nsContentUtils.h | 19 ++++++++++++++++--- dom/bindings/Configuration.py | 1 + dom/bindings/parser/WebIDL.py | 2 +- dom/chrome-webidl/InspectorUtils.webidl | 4 ++-- js/xpconnect/src/XPCJSContext.cpp | 3 ++- modules/libpref/init/StaticPrefList.h | 14 ++++++++++++++ modules/libpref/init/all.js | 4 ---- 8 files changed, 44 insertions(+), 11 deletions(-) diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 0c992c2262e8..dc6fa7bc5e0a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -2211,6 +2211,14 @@ nsContentUtils::IsCallerChrome() return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext()); } +#ifdef FUZZING +bool +nsContentUtils::IsFuzzingEnabled() +{ + return StaticPrefs::fuzzing_enabled(); +} +#endif + /* static */ bool nsContentUtils::ShouldResistFingerprinting() diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 00e41f4679f9..7bddbce14028 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -216,9 +216,22 @@ public: // Strip off "wyciwyg://n/" part of a URL. aURI must have "wyciwyg" scheme. static nsresult RemoveWyciwygScheme(nsIURI* aURI, nsIURI** aReturn); - static bool IsCallerChrome(); - static bool ThreadsafeIsCallerChrome(); - static bool IsCallerContentXBL(); + static bool IsCallerChrome(); + static bool ThreadsafeIsCallerChrome(); + static bool IsCallerContentXBL(); + static bool IsFuzzingEnabled() +#ifndef FUZZING + { + return false; + } +#else + ; +#endif + + static bool IsCallerChromeOrFuzzingEnabled(JSContext* aCx, JSObject*) + { + return ThreadsafeIsSystemCaller(aCx) || IsFuzzingEnabled(); + } // The APIs for checking whether the caller is system (in the sense of system // principal) should only be used when the JSContext is known to accurately diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index f8bf2bad38b4..79056d62f289 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -105,6 +105,7 @@ class Configuration(DescriptorProvider): (partialIface.location, iface.location)) if not (iface.getExtendedAttribute("ChromeOnly") or iface.getExtendedAttribute("Func") == ["IsChromeOrXBL"] or + iface.getExtendedAttribute("Func") == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"] or not (iface.hasInterfaceObject() or iface.isNavigatorProperty()) or isInWebIDLRoot(iface.filename())): diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index f4f9cb1323a5..d484a4a3e63a 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1809,7 +1809,7 @@ class IDLNamespace(IDLInterfaceOrNamespace): if not attr.noArguments(): raise WebIDLError("[%s] must not have arguments" % identifier, [attr.location]) - elif identifier == "Pref": + elif identifier == "Pref" or identifier == "Func": # Known extended attributes that take a string value if not attr.hasValue(): raise WebIDLError("[%s] must have a value" % identifier, diff --git a/dom/chrome-webidl/InspectorUtils.webidl b/dom/chrome-webidl/InspectorUtils.webidl index 98dff60c9c56..9d22d1fc6e7f 100644 --- a/dom/chrome-webidl/InspectorUtils.webidl +++ b/dom/chrome-webidl/InspectorUtils.webidl @@ -9,7 +9,7 @@ * * See InspectorUtils.h for documentation on these methods. */ -[ChromeOnly] +[Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled"] namespace InspectorUtils { // documentOnly tells whether user and UA sheets should get included. sequence getAllStyleSheets(Document document, optional boolean documentOnly = false); @@ -129,7 +129,7 @@ dictionary InspectorFontFeature { required DOMString languageSystem; }; -[ChromeOnly] +[Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled"] interface InspectorFontFace { // An indication of how we found this font during font-matching. // Note that the same physical font may have been found in multiple ways within a range. diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index 26727726f72f..d53c05abc67d 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -30,6 +30,7 @@ #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/Services.h" +#include "mozilla/StaticPrefs.h" #include "mozilla/dom/ScriptSettings.h" #include "nsContentUtils.h" @@ -861,7 +862,7 @@ ReloadPrefsCallback(const char* pref, XPCJSContext* xpccx) #endif // JS_GC_ZEAL #ifdef FUZZING - bool fuzzingEnabled = Preferences::GetBool("fuzzing.enabled"); + bool fuzzingEnabled = StaticPrefs::fuzzing_enabled(); #endif JS::ContextOptionsRef(cx).setBaseline(useBaseline) diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h index 04d94adb5197..20ce46fc8acb 100644 --- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -87,6 +87,20 @@ VARCACHE_PREF( RelaxedAtomicBool, false ) +//--------------------------------------------------------------------------- +// Fuzzing prefs. It's important that these can only be checked in fuzzing +// builds (when FUZZING is defined), otherwise you could enable the fuzzing +// stuff on your regular build which would be bad :) +//--------------------------------------------------------------------------- + +#ifdef FUZZING +VARCACHE_PREF( + "fuzzing.enabled", + fuzzing_enabled, + bool, false +) +#endif + //--------------------------------------------------------------------------- // Clipboard prefs //--------------------------------------------------------------------------- diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 757953a6c5cd..7cccf1d10758 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5813,10 +5813,6 @@ pref("dom.payments.loglevel", "Warn"); pref("dom.payments.defaults.saveCreditCard", false); pref("dom.payments.defaults.saveAddress", true); -#ifdef FUZZING -pref("fuzzing.enabled", false); -#endif - #ifdef MOZ_ASAN_REPORTER pref("asanreporter.apiurl", "https://anf1.fuzzing.mozilla.org/crashproxy/submit/"); pref("asanreporter.clientid", "unknown"); From b0a0465f3967abee16be0c67cfc4adb8fd570007 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Thu, 27 Sep 2018 20:09:51 +0000 Subject: [PATCH 09/25] Bug 1491471 - Move the OI store to the console. r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D5911 --HG-- rename : devtools/client/shared/components/reps/reps.js => devtools/client/shared/components/reps/reps-old.js extra : moz-landing-system : lando --- .../new/src/client/firefox/commands.js | 14 + .../src/components/Editor/Preview/Popup.js | 25 +- .../components/SecondaryPanes/Expressions.js | 6 +- .../SecondaryPanes/FrameworkComponent.js | 17 +- .../src/components/SecondaryPanes/Scopes.js | 6 +- .../client/debugger/new/src/reducers/index.js | 5 +- .../debugger/new/src/selectors/index.js | 16 + .../new/src/utils/pause/scopes/getScope.js | 4 +- .../test/mochitest/browser_dbg-expressions.js | 4 +- .../new/test/mochitest/browser_dbg-scopes.js | 2 +- .../extensions/components/ObjectTreeView.js | 3 +- .../components/ObjectValueGripView.js | 4 +- .../client/shared/components/reps/moz.build | 1 + .../client/shared/components/reps/reps-old.js | 7073 +++++++++++++++++ .../client/shared/components/reps/reps.js | 3865 +++++---- devtools/client/webconsole/actions/filters.js | 6 +- .../client/webconsole/actions/messages.js | 2 +- devtools/client/webconsole/actions/ui.js | 6 +- .../webconsole/components/GripMessageBody.js | 4 +- .../client/webconsole/middleware/thunk.js | 2 +- devtools/client/webconsole/reducers/index.js | 3 + devtools/client/webconsole/store.js | 2 +- .../test/components/console-api-call.test.js | 27 +- .../test/components/console-output.test.js | 11 +- .../test/components/evaluation-result.test.js | 31 +- .../new-console-output-wrapper.test.js | 50 +- .../webconsole/utils/object-inspector.js | 19 +- .../client/webconsole/webconsole-frame.js | 10 +- .../webconsole/webconsole-output-wrapper.js | 42 +- 29 files changed, 9146 insertions(+), 2114 deletions(-) create mode 100644 devtools/client/shared/components/reps/reps-old.js diff --git a/devtools/client/debugger/new/src/client/firefox/commands.js b/devtools/client/debugger/new/src/client/firefox/commands.js index fdc90d2fa4f8..16415c5620bc 100644 --- a/devtools/client/debugger/new/src/client/firefox/commands.js +++ b/devtools/client/debugger/new/src/client/firefox/commands.js @@ -35,6 +35,18 @@ function setupCommands(dependencies) { }; } +function createObjectClient(grip) { + return debuggerClient.createObjectClient(grip); +} + +function releaseActor(actor) { + if (!actor) { + return; + } + + return debuggerClient.release(actor); +} + function sendPacket(packet, callback = r => r) { return debuggerClient.request(packet).then(callback); } @@ -406,6 +418,8 @@ async function fetchWorkers() { const clientCommands = { autocomplete, blackBox, + createObjectClient, + releaseActor, interrupt, eventListeners, pauseGrip, diff --git a/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js b/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js index 483497d6d4a2..ec3e3fc2a46c 100644 --- a/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js +++ b/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js @@ -47,19 +47,24 @@ const { Rep }, MODE, - ObjectInspector, - ObjectInspectorUtils + objectInspector } = _devtoolsReps2.default; const { - createNode, - getChildren, - getValue, - nodeIsPrimitive, - NODE_TYPES -} = ObjectInspectorUtils.node; + ObjectInspector, + utils +} = objectInspector; const { - loadItemProperties -} = ObjectInspectorUtils.loadProperties; + node: { + createNode, + getChildren, + getValue, + nodeIsPrimitive, + NODE_TYPES + }, + loadProperties: { + loadItemProperties + } +} = utils; function inPreview(event) { const relatedTarget = event.relatedTarget; diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js index 8bb90a246043..1877edb1524c 100644 --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js @@ -36,6 +36,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +const { + ObjectInspector +} = _devtoolsReps.objectInspector; + class Expressions extends _react.Component { constructor(props) { super(props); @@ -157,7 +161,7 @@ class Expressions extends _react.Component { onDoubleClick: (items, options) => this.editExpression(expression, index) }, _react2.default.createElement("div", { className: "expression-content" - }, _react2.default.createElement(_devtoolsReps.ObjectInspector, { + }, _react2.default.createElement(ObjectInspector, { roots: [root], autoExpandDepth: 0, disableWrap: true, diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js b/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js index 68ca346e5554..382ed5f768a3 100644 --- a/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js @@ -28,12 +28,15 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ const { - createNode, - getChildren -} = _devtoolsReps.ObjectInspectorUtils.node; -const { - loadItemProperties -} = _devtoolsReps.ObjectInspectorUtils.loadProperties; + component: ObjectInspector, + utils: { + createNode, + getChildren, + loadProperties: { + loadItemProperties + } + } +} = _devtoolsReps.objectInspector; class FrameworkComponent extends _react.PureComponent { async componentWillMount() { @@ -83,7 +86,7 @@ class FrameworkComponent extends _react.PureComponent { roots = roots.filter(r => ["state", "props"].includes(r.name)); return _react2.default.createElement("div", { className: "pane framework-component" - }, _react2.default.createElement(_devtoolsReps.ObjectInspector, { + }, _react2.default.createElement(ObjectInspector, { roots: roots, autoExpandAll: false, autoExpandDepth: 0, diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js index 6ff37b5771c5..35a205e632e4 100644 --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js @@ -27,6 +27,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ +const { + ObjectInspector +} = _devtoolsReps.objectInspector; + class Scopes extends _react.PureComponent { constructor(props, ...args) { const { @@ -79,7 +83,7 @@ class Scopes extends _react.PureComponent { if (scopes && !isLoading) { return _react2.default.createElement("div", { className: "pane scopes-list" - }, _react2.default.createElement(_devtoolsReps.ObjectInspector, { + }, _react2.default.createElement(ObjectInspector, { roots: scopes, autoExpandAll: false, autoExpandDepth: 1, diff --git a/devtools/client/debugger/new/src/reducers/index.js b/devtools/client/debugger/new/src/reducers/index.js index 6fdcbad0382d..119b462ec75d 100644 --- a/devtools/client/debugger/new/src/reducers/index.js +++ b/devtools/client/debugger/new/src/reducers/index.js @@ -68,6 +68,8 @@ var _debuggee = require("./debuggee"); var _debuggee2 = _interopRequireDefault(_debuggee); +var _devtoolsReps = require("devtools/client/shared/components/reps/reps.js"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* This Source Code Form is subject to the terms of the Mozilla Public @@ -94,5 +96,6 @@ exports.default = { projectTextSearch: _projectTextSearch2.default, quickOpen: _quickOpen2.default, sourceTree: _sourceTree2.default, - debuggee: _debuggee2.default + debuggee: _debuggee2.default, + objectInspector: _devtoolsReps.objectInspector.reducer.default }; \ No newline at end of file diff --git a/devtools/client/debugger/new/src/selectors/index.js b/devtools/client/debugger/new/src/selectors/index.js index 0dd19aed5762..96e92a72c29f 100644 --- a/devtools/client/debugger/new/src/selectors/index.js +++ b/devtools/client/debugger/new/src/selectors/index.js @@ -257,4 +257,20 @@ Object.defineProperty(exports, "getBreakpointSources", { get: function () { return _breakpointSources.getBreakpointSources; } +}); + +var _devtoolsReps = require("devtools/client/shared/components/reps/reps.js"); + +const { + reducer +} = _devtoolsReps.objectInspector; +Object.keys(reducer).forEach(function (key) { + if (key === "default" || key === "__esModule") { + return; + } + + Object.defineProperty(exports, key, { + enumerable: true, + get: reducer[key] + }); }); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/utils/pause/scopes/getScope.js b/devtools/client/debugger/new/src/utils/pause/scopes/getScope.js index 990e985c11bf..31badd706ac8 100644 --- a/devtools/client/debugger/new/src/utils/pause/scopes/getScope.js +++ b/devtools/client/debugger/new/src/utils/pause/scopes/getScope.js @@ -16,6 +16,8 @@ var _frames = require("../../pause/frames/index"); /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ +const { utils: { node: { NODE_TYPES } } } = _devtoolsReps.objectInspector; + function getScopeTitle(type, scope) { if (type === "block" && scope.block && scope.block.displayName) { return scope.block.displayName; @@ -65,7 +67,7 @@ function getScope(scope, selectedFrame, frameScopes, why, scopeIndex) { name: title, path: key, contents: vars, - type: _devtoolsReps.ObjectInspectorUtils.node.NODE_TYPES.BLOCK + type: NODE_TYPES.BLOCK }; } } else if (type === "object" && scope.object) { diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js index 7073e7ec5f35..07bdb5f466ee 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js @@ -87,10 +87,12 @@ add_task(async function() { // Test expanding properties when the debuggee is active await resume(dbg); await addExpression(dbg, "location"); - await toggleExpressionNode(dbg, 1); is(findAllElements(dbg, "expressionNodes").length, 17); + await toggleExpressionNode(dbg, 1); + is(findAllElements(dbg, "expressionNodes").length, 1); + await deleteExpression(dbg, "location"); is(findAllElements(dbg, "expressionNodes").length, 0); }); diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js index cec701b270ad..8b13aa30df67 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js @@ -24,5 +24,5 @@ add_task(async function() { await stepOver(dbg); is(getLabel(dbg, 4), "foo()"); - is(getLabel(dbg, 5), "Window"); + is(getLabel(dbg, 11), "Window"); }); diff --git a/devtools/client/inspector/extensions/components/ObjectTreeView.js b/devtools/client/inspector/extensions/components/ObjectTreeView.js index b0af620b91a0..8423ae73656c 100644 --- a/devtools/client/inspector/extensions/components/ObjectTreeView.js +++ b/devtools/client/inspector/extensions/components/ObjectTreeView.js @@ -7,7 +7,8 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { REPS, MODE } = require("devtools/client/shared/components/reps/reps"); +// TODO: Upgrade to the current version reps https://bugzilla.mozilla.org/show_bug.cgi?id=1494680 +const { REPS, MODE } = require("devtools/client/shared/components/reps/reps-old"); const { Rep } = REPS; const TreeViewClass = require("devtools/client/shared/components/tree/TreeView"); const TreeView = createFactory(TreeViewClass); diff --git a/devtools/client/inspector/extensions/components/ObjectValueGripView.js b/devtools/client/inspector/extensions/components/ObjectValueGripView.js index 99efd5ffe256..cce3f15c1b26 100644 --- a/devtools/client/inspector/extensions/components/ObjectValueGripView.js +++ b/devtools/client/inspector/extensions/components/ObjectValueGripView.js @@ -8,7 +8,9 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/ const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion")); -const reps = require("devtools/client/shared/components/reps/reps"); + +// TODO: Upgrade to the current version reps https://bugzilla.mozilla.org/show_bug.cgi?id=1494680 +const reps = require("devtools/client/shared/components/reps/reps-old"); const Types = require("../types"); const { REPS, MODE } = reps; diff --git a/devtools/client/shared/components/reps/moz.build b/devtools/client/shared/components/reps/moz.build index 08b7950f5691..86a9a8afa727 100644 --- a/devtools/client/shared/components/reps/moz.build +++ b/devtools/client/shared/components/reps/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'reps-old.js', 'reps.css', 'reps.js', ) diff --git a/devtools/client/shared/components/reps/reps-old.js b/devtools/client/shared/components/reps/reps-old.js new file mode 100644 index 000000000000..b4988e913e11 --- /dev/null +++ b/devtools/client/shared/components/reps/reps-old.js @@ -0,0 +1,7073 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")); + else if(typeof define === 'function' && define.amd) + define(["devtools/client/shared/vendor/react", "Services", "devtools/client/shared/vendor/react-redux", "devtools/client/shared/vendor/redux", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories"], factory); + else { + var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")) : factory(root["devtools/client/shared/vendor/react"], root["Services"], root["devtools/client/shared/vendor/react-redux"], root["devtools/client/shared/vendor/redux"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"]); + for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; + } +})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_3592__, __WEBPACK_EXTERNAL_MODULE_3593__, __WEBPACK_EXTERNAL_MODULE_3642__, __WEBPACK_EXTERNAL_MODULE_3643__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "/assets/build"; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 3730); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ 0: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_0__; + +/***/ }), + +/***/ 175: +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! + Copyright (c) 2017 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +/* global define */ + +(function () { + 'use strict'; + + var hasOwn = {}.hasOwnProperty; + + function classNames () { + var classes = []; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (!arg) continue; + + var argType = typeof arg; + + if (argType === 'string' || argType === 'number') { + classes.push(arg); + } else if (Array.isArray(arg) && arg.length) { + var inner = classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } else if (argType === 'object') { + for (var key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(' '); + } + + if (typeof module !== 'undefined' && module.exports) { + classNames.default = classNames; + module.exports = classNames; + } else if (true) { + // register as 'classnames', consistent with npm package name + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = (function () { + return classNames; + }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else { + window.classNames = classNames; + } +}()); + + +/***/ }), + +/***/ 22: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_22__; + +/***/ }), + +/***/ 3592: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_3592__; + +/***/ }), + +/***/ 3593: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_3593__; + +/***/ }), + +/***/ 3642: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_3642__; + +/***/ }), + +/***/ 3643: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_3643__; + +/***/ }), + +/***/ 3644: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const validProtocols = /(http|https|ftp|data|resource|chrome):/i; +const tokenSplitRegex = /(\s|\'|\"|\\)+/; +const ELLIPSIS = "\u2026"; +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Returns true if the given object is a grip (see RDP protocol) + */ +function isGrip(object) { + return object && object.actor; +} + +function escapeNewLines(value) { + return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n"); +} + +// Map from character code to the corresponding escape sequence. \0 +// isn't here because it would require special treatment in some +// situations. \b, \f, and \v aren't here because they aren't very +// common. \' isn't here because there's no need, we only +// double-quote strings. +const escapeMap = { + // Tab. + 9: "\\t", + // Newline. + 0xa: "\\n", + // Carriage return. + 0xd: "\\r", + // Quote. + 0x22: '\\"', + // Backslash. + 0x5c: "\\\\" +}; + +// Regexp that matches any character we might possibly want to escape. +// Note that we over-match here, because it's difficult to, say, match +// an unpaired surrogate with a regexp. The details are worked out by +// the replacement function; see |escapeString|. +const escapeRegexp = new RegExp("[" + +// Quote and backslash. +'"\\\\' + +// Controls. +"\x00-\x1f" + +// More controls. +"\x7f-\x9f" + +// BOM +"\ufeff" + +// Specials, except for the replacement character. +"\ufff0-\ufffc\ufffe\uffff" + +// Surrogates. +"\ud800-\udfff" + +// Mathematical invisibles. +"\u2061-\u2064" + +// Line and paragraph separators. +"\u2028-\u2029" + +// Private use area. +"\ue000-\uf8ff" + "]", "g"); + +/** + * Escape a string so that the result is viewable and valid JS. + * Control characters, other invisibles, invalid characters, + * backslash, and double quotes are escaped. The resulting string is + * surrounded by double quotes. + * + * @param {String} str + * the input + * @param {Boolean} escapeWhitespace + * if true, TAB, CR, and NL characters will be escaped + * @return {String} the escaped string + */ +function escapeString(str, escapeWhitespace) { + return `"${str.replace(escapeRegexp, (match, offset) => { + const c = match.charCodeAt(0); + if (c in escapeMap) { + if (!escapeWhitespace && (c === 9 || c === 0xa || c === 0xd)) { + return match[0]; + } + return escapeMap[c]; + } + if (c >= 0xd800 && c <= 0xdfff) { + // Find the full code point containing the surrogate, with a + // special case for a trailing surrogate at the start of the + // string. + if (c >= 0xdc00 && offset > 0) { + --offset; + } + const codePoint = str.codePointAt(offset); + if (codePoint >= 0xd800 && codePoint <= 0xdfff) { + // Unpaired surrogate. + return `\\u${codePoint.toString(16)}`; + } else if (codePoint >= 0xf0000 && codePoint <= 0x10fffd) { + // Private use area. Because we visit each pair of a such a + // character, return the empty string for one half and the + // real result for the other, to avoid duplication. + if (c <= 0xdbff) { + return `\\u{${codePoint.toString(16)}}`; + } + return ""; + } + // Other surrogate characters are passed through. + return match; + } + return `\\u${`0000${c.toString(16)}`.substr(-4)}`; + })}"`; +} + +/** + * Escape a property name, if needed. "Escaping" in this context + * means surrounding the property name with quotes. + * + * @param {String} + * name the property name + * @return {String} either the input, or the input surrounded by + * quotes, properly quoted in JS syntax. + */ +function maybeEscapePropertyName(name) { + // Quote the property name if it needs quoting. This particular + // test is an approximation; see + // https://mathiasbynens.be/notes/javascript-properties. However, + // the full solution requires a fair amount of Unicode data, and so + // let's defer that until either it's important, or the \p regexp + // syntax lands, see + // https://github.com/tc39/proposal-regexp-unicode-property-escapes. + if (!/^\w+$/.test(name)) { + name = escapeString(name); + } + return name; +} + +function cropMultipleLines(text, limit) { + return escapeNewLines(cropString(text, limit)); +} + +function rawCropString(text, limit, alternativeText = ELLIPSIS) { + // Crop the string only if a limit is actually specified. + if (!limit || limit <= 0) { + return text; + } + + // Set the limit at least to the length of the alternative text + // plus one character of the original text. + if (limit <= alternativeText.length) { + limit = alternativeText.length + 1; + } + + const halfLimit = (limit - alternativeText.length) / 2; + + if (text.length > limit) { + return text.substr(0, Math.ceil(halfLimit)) + alternativeText + text.substr(text.length - Math.floor(halfLimit)); + } + + return text; +} + +function cropString(text, limit, alternativeText) { + return rawCropString(sanitizeString(`${text}`), limit, alternativeText); +} + +function sanitizeString(text) { + // Replace all non-printable characters, except of + // (horizontal) tab (HT: \x09) and newline (LF: \x0A, CR: \x0D), + // with unicode replacement character (u+fffd). + // eslint-disable-next-line no-control-regex + const re = new RegExp("[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]", "g"); + return text.replace(re, "\ufffd"); +} + +function parseURLParams(url) { + url = new URL(url); + return parseURLEncodedText(url.searchParams); +} + +function parseURLEncodedText(text) { + const params = []; + + // In case the text is empty just return the empty parameters + if (text == "") { + return params; + } + + const searchParams = new URLSearchParams(text); + const entries = [...searchParams.entries()]; + return entries.map(entry => { + return { + name: entry[0], + value: entry[1] + }; + }); +} + +function getFileName(url) { + const split = splitURLBase(url); + return split.name; +} + +function splitURLBase(url) { + if (!isDataURL(url)) { + return splitURLTrue(url); + } + return {}; +} + +function getURLDisplayString(url) { + return cropString(url); +} + +function isDataURL(url) { + return url && url.substr(0, 5) == "data:"; +} + +function splitURLTrue(url) { + const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/; + const m = reSplitFile.exec(url); + + if (!m) { + return { + name: url, + path: url + }; + } else if (m[4] == "" && m[5] == "") { + return { + protocol: m[1], + domain: m[2], + path: m[3], + name: m[3] != "/" ? m[3] : m[2] + }; + } + + return { + protocol: m[1], + domain: m[2], + path: m[2] + m[3], + name: m[4] + m[5] + }; +} + +/** + * Wrap the provided render() method of a rep in a try/catch block that will + * render a fallback rep if the render fails. + */ +function wrapRender(renderMethod) { + const wrappedFunction = function (props) { + try { + return renderMethod.call(this, props); + } catch (e) { + console.error(e); + return span({ + className: "objectBox objectBox-failure", + title: "This object could not be rendered, " + "please file a bug on bugzilla.mozilla.org" + }, + /* Labels have to be hardcoded for reps, see Bug 1317038. */ + "Invalid object"); + } + }; + wrappedFunction.propTypes = renderMethod.propTypes; + return wrappedFunction; +} + +/** + * Get preview items from a Grip. + * + * @param {Object} Grip from which we want the preview items + * @return {Array} Array of the preview items of the grip, or an empty array + * if the grip does not have preview items + */ +function getGripPreviewItems(grip) { + if (!grip) { + return []; + } + + // Promise resolved value Grip + if (grip.promiseState && grip.promiseState.value) { + return [grip.promiseState.value]; + } + + // Array Grip + if (grip.preview && grip.preview.items) { + return grip.preview.items; + } + + // Node Grip + if (grip.preview && grip.preview.childNodes) { + return grip.preview.childNodes; + } + + // Set or Map Grip + if (grip.preview && grip.preview.entries) { + return grip.preview.entries.reduce((res, entry) => res.concat(entry), []); + } + + // Event Grip + if (grip.preview && grip.preview.target) { + const keys = Object.keys(grip.preview.properties); + const values = Object.values(grip.preview.properties); + return [grip.preview.target, ...keys, ...values]; + } + + // RegEx Grip + if (grip.displayString) { + return [grip.displayString]; + } + + // Generic Grip + if (grip.preview && grip.preview.ownProperties) { + let propertiesValues = Object.values(grip.preview.ownProperties).map(property => property.value || property); + + const propertyKeys = Object.keys(grip.preview.ownProperties); + propertiesValues = propertiesValues.concat(propertyKeys); + + // ArrayBuffer Grip + if (grip.preview.safeGetterValues) { + propertiesValues = propertiesValues.concat(Object.values(grip.preview.safeGetterValues).map(property => property.getterValue || property)); + } + + return propertiesValues; + } + + return []; +} + +/** + * Get the type of an object. + * + * @param {Object} Grip from which we want the type. + * @param {boolean} noGrip true if the object is not a grip. + * @return {boolean} + */ +function getGripType(object, noGrip) { + if (noGrip || Object(object) !== object) { + return typeof object; + } + if (object.type === "object") { + return object.class; + } + return object.type; +} + +/** + * Determines whether a grip is a string containing a URL. + * + * @param string grip + * The grip, which may contain a URL. + * @return boolean + * Whether the grip is a string containing a URL. + */ +function containsURL(grip) { + // An URL can't be shorter than 5 char (e.g. "ftp:"). + if (typeof grip !== "string" || grip.length < 5) { + return false; + } + + return validProtocols.test(grip); +} + +/** + * Determines whether a string token is a valid URL. + * + * @param string token + * The token. + * @return boolean + * Whenther the token is a URL. + */ +function isURL(token) { + try { + if (!validProtocols.test(token)) { + return false; + } + new URL(token); + return true; + } catch (e) { + return false; + } +} + +/** + * Returns new array in which `char` are interleaved between the original items. + * + * @param {Array} items + * @param {String} char + * @returns Array + */ +function interleave(items, char) { + return items.reduce((res, item, index) => { + if (index !== items.length - 1) { + return res.concat(item, char); + } + return res.concat(item); + }, []); +} + +const ellipsisElement = span({ + key: "more", + className: "more-ellipsis", + title: `more${ELLIPSIS}` +}, ELLIPSIS); + +module.exports = { + interleave, + isGrip, + isURL, + cropString, + containsURL, + rawCropString, + sanitizeString, + escapeString, + wrapRender, + cropMultipleLines, + parseURLParams, + parseURLEncodedText, + getFileName, + getURLDisplayString, + maybeEscapePropertyName, + getGripPreviewItems, + getGripType, + tokenSplitRegex, + ellipsisElement, + ELLIPSIS +}; + +/***/ }), + +/***/ 3645: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +module.exports = { + MODE: { + TINY: Symbol("TINY"), + SHORT: Symbol("SHORT"), + LONG: Symbol("LONG") + } +}; + +/***/ }), + +/***/ 3647: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +__webpack_require__(3672); + +// Load all existing rep templates +const Undefined = __webpack_require__(3673); +const Null = __webpack_require__(3674); +const StringRep = __webpack_require__(3648); +const Number = __webpack_require__(3675); +const ArrayRep = __webpack_require__(3649); +const Obj = __webpack_require__(3676); +const SymbolRep = __webpack_require__(3677); +const InfinityRep = __webpack_require__(3678); +const NaNRep = __webpack_require__(3679); +const Accessor = __webpack_require__(3680); + +// DOM types (grips) +const Accessible = __webpack_require__(3787); +const Attribute = __webpack_require__(3681); +const DateTime = __webpack_require__(3682); +const Document = __webpack_require__(3683); +const DocumentType = __webpack_require__(3684); +const Event = __webpack_require__(3685); +const Func = __webpack_require__(3658); +const PromiseRep = __webpack_require__(3686); +const RegExp = __webpack_require__(3687); +const StyleSheet = __webpack_require__(3688); +const CommentNode = __webpack_require__(3689); +const ElementNode = __webpack_require__(3690); +const TextNode = __webpack_require__(3691); +const ErrorRep = __webpack_require__(3660); +const Window = __webpack_require__(3692); +const ObjectWithText = __webpack_require__(3693); +const ObjectWithURL = __webpack_require__(3694); +const GripArray = __webpack_require__(3661); +const GripMap = __webpack_require__(3663); +const GripMapEntry = __webpack_require__(3664); +const Grip = __webpack_require__(3656); + +// List of all registered template. +// XXX there should be a way for extensions to register a new +// or modify an existing rep. +const reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, Accessible, ElementNode, TextNode, Attribute, Func, PromiseRep, ArrayRep, Document, DocumentType, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor, Obj]; + +/** + * Generic rep that is used for rendering native JS types or an object. + * The right template used for rendering is picked automatically according + * to the current value type. The value must be passed in as the 'object' + * property. + */ +const Rep = function (props) { + const { object, defaultRep } = props; + const rep = getRep(object, defaultRep, props.noGrip); + return rep(props); +}; + +// Helpers + +/** + * Return a rep object that is responsible for rendering given + * object. + * + * @param object {Object} Object to be rendered in the UI. This + * can be generic JS object as well as a grip (handle to a remote + * debuggee object). + * + * @param defaultRep {React.Component} The default template + * that should be used to render given object if none is found. + * + * @param noGrip {Boolean} If true, will only check reps not made for remote + * objects. + */ +function getRep(object, defaultRep = Grip, noGrip = false) { + for (let i = 0; i < reps.length; i++) { + const rep = reps[i]; + try { + // supportsObject could return weight (not only true/false + // but a number), which would allow to priorities templates and + // support better extensibility. + if (rep.supportsObject(object, noGrip)) { + return rep.rep; + } + } catch (err) { + console.error(err); + } + } + + return defaultRep.rep; +} + +module.exports = { + Rep, + REPS: { + Accessible, + Accessor, + ArrayRep, + Attribute, + CommentNode, + DateTime, + Document, + DocumentType, + ElementNode, + ErrorRep, + Event, + Func, + Grip, + GripArray, + GripMap, + GripMapEntry, + InfinityRep, + NaNRep, + Null, + Number, + Obj, + ObjectWithText, + ObjectWithURL, + PromiseRep, + RegExp, + Rep, + StringRep, + StyleSheet, + SymbolRep, + TextNode, + Undefined, + Window + }, + // Exporting for tests + getRep +}; + +/***/ }), + +/***/ 3648: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); + +const { + containsURL, + isURL, + escapeString, + getGripType, + rawCropString, + sanitizeString, + wrapRender, + isGrip, + tokenSplitRegex, + ELLIPSIS +} = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { a, span } = dom; + +/** + * Renders a string. String value is enclosed within quotes. + */ +StringRep.propTypes = { + useQuotes: PropTypes.bool, + escapeWhitespace: PropTypes.bool, + style: PropTypes.object, + cropLimit: PropTypes.number.isRequired, + member: PropTypes.object, + object: PropTypes.object.isRequired, + openLink: PropTypes.func, + className: PropTypes.string +}; + +function StringRep(props) { + const { + className, + style, + cropLimit, + object, + useQuotes = true, + escapeWhitespace = true, + member, + openLink + } = props; + + let text = object; + + const isLong = isLongString(object); + const isOpen = member && member.open; + const shouldCrop = !isOpen && cropLimit && text.length > cropLimit; + + if (isLong) { + text = maybeCropLongString({ + shouldCrop, + cropLimit + }, text); + + const { fullText } = object; + if (isOpen && fullText) { + text = fullText; + } + } + + text = formatText({ + useQuotes, + escapeWhitespace + }, text); + + const config = getElementConfig({ + className, + style, + actor: object.actor + }); + + if (!isLong) { + if (containsURL(text)) { + return span(config, ...getLinkifiedElements(text, shouldCrop && cropLimit, openLink)); + } + + // Cropping of longString has been handled before formatting. + text = maybeCropString({ + isLong, + shouldCrop, + cropLimit + }, text); + } + + return span(config, text); +} + +function maybeCropLongString(opts, text) { + const { shouldCrop, cropLimit } = opts; + + const { initial, length } = text; + + text = shouldCrop ? initial.substring(0, cropLimit) : initial; + + if (text.length < length) { + text += ELLIPSIS; + } + + return text; +} + +function formatText(opts, text) { + const { useQuotes, escapeWhitespace } = opts; + + return useQuotes ? escapeString(text, escapeWhitespace) : sanitizeString(text); +} + +function getElementConfig(opts) { + const { className, style, actor } = opts; + + const config = {}; + + if (actor) { + config["data-link-actor-id"] = actor; + } + + const classNames = ["objectBox", "objectBox-string"]; + if (className) { + classNames.push(className); + } + config.className = classNames.join(" "); + + if (style) { + config.style = style; + } + + return config; +} + +function maybeCropString(opts, text) { + const { shouldCrop, cropLimit } = opts; + + return shouldCrop ? rawCropString(text, cropLimit) : text; +} + +/** + * Get an array of the elements representing the string, cropped if needed, + * with actual links. + * + * @param {String} text: The actual string to linkify. + * @param {Integer | null} cropLimit + * @param {Function} openLink: Function handling the link opening. + * @returns {Array} + */ +function getLinkifiedElements(text, cropLimit, openLink) { + const halfLimit = Math.ceil((cropLimit - ELLIPSIS.length) / 2); + const startCropIndex = cropLimit ? halfLimit : null; + const endCropIndex = cropLimit ? text.length - halfLimit : null; + + // As we walk through the tokens of the source string, we make sure to + // preserve the original whitespace that separated the tokens. + let currentIndex = 0; + const items = []; + for (const token of text.split(tokenSplitRegex)) { + if (isURL(token)) { + // Let's grab all the non-url strings before the link. + const tokenStart = text.indexOf(token, currentIndex); + let nonUrlText = text.slice(currentIndex, tokenStart); + nonUrlText = getCroppedString(nonUrlText, currentIndex, startCropIndex, endCropIndex); + if (nonUrlText) { + items.push(nonUrlText); + } + + // Update the index to match the beginning of the token. + currentIndex = tokenStart; + + const linkText = getCroppedString(token, currentIndex, startCropIndex, endCropIndex); + if (linkText) { + items.push(a({ + className: "url", + title: token, + draggable: false, + onClick: openLink ? e => { + e.preventDefault(); + openLink(token, e); + } : null + }, linkText)); + } + + currentIndex = tokenStart + token.length; + } + } + + // Clean up any non-URL text at the end of the source string, + // i.e. not handled in the loop. + if (currentIndex !== text.length) { + let nonUrlText = text.slice(currentIndex, text.length); + if (currentIndex < endCropIndex) { + nonUrlText = getCroppedString(nonUrlText, currentIndex, startCropIndex, endCropIndex); + } + items.push(nonUrlText); + } + + return items; +} + +/** + * Returns a cropped substring given an offset, start and end crop indices in a + * parent string. + * + * @param {String} text: The substring to crop. + * @param {Integer} offset: The offset corresponding to the index at which + * the substring is in the parent string. + * @param {Integer|null} startCropIndex: the index where the start of the crop + * should happen in the parent string. + * @param {Integer|null} endCropIndex: the index where the end of the crop + * should happen in the parent string + * @returns {String|null} The cropped substring, or null if the text is + * completly cropped. + */ +function getCroppedString(text, offset = 0, startCropIndex, endCropIndex) { + if (!startCropIndex) { + return text; + } + + const start = offset; + const end = offset + text.length; + + const shouldBeVisible = !(start >= startCropIndex && end <= endCropIndex); + if (!shouldBeVisible) { + return null; + } + + const shouldCropEnd = start < startCropIndex && end > startCropIndex; + const shouldCropStart = start < endCropIndex && end > endCropIndex; + if (shouldCropEnd) { + const cutIndex = startCropIndex - start; + return text.substring(0, cutIndex) + ELLIPSIS + (shouldCropStart ? text.substring(endCropIndex - start) : ""); + } + + if (shouldCropStart) { + // The string should be cropped at the beginning. + const cutIndex = endCropIndex - start; + return text.substring(cutIndex); + } + + return text; +} + +function isLongString(object) { + return object && object.type === "longString"; +} + +function supportsObject(object, noGrip = false) { + if (noGrip === false && isGrip(object)) { + return isLongString(object); + } + + return getGripType(object, noGrip) == "string"; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(StringRep), + supportsObject, + isLongString +}; + +/***/ }), + +/***/ 3649: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const dom = __webpack_require__(3643); +const PropTypes = __webpack_require__(3642); +const { wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); +const { span } = dom; + +const ModePropType = PropTypes.oneOf( +// @TODO Change this to Object.values when supported in Node's version of V8 +Object.keys(MODE).map(key => MODE[key])); + +/** + * Renders an array. The array is enclosed by left and right bracket + * and the max number of rendered items depends on the current mode. + */ +ArrayRep.propTypes = { + mode: ModePropType, + object: PropTypes.array.isRequired +}; + +function ArrayRep(props) { + const { object, mode = MODE.SHORT } = props; + + let items; + let brackets; + const needSpace = function (space) { + return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" }; + }; + + if (mode === MODE.TINY) { + const isEmpty = object.length === 0; + if (isEmpty) { + items = []; + } else { + items = [span({ + className: "more-ellipsis", + title: "more…" + }, "…")]; + } + brackets = needSpace(false); + } else { + items = arrayIterator(props, object, maxLengthMap.get(mode)); + brackets = needSpace(items.length > 0); + } + + return span({ + className: "objectBox objectBox-array" + }, span({ + className: "arrayLeftBracket" + }, brackets.left), ...items, span({ + className: "arrayRightBracket" + }, brackets.right)); +} + +function arrayIterator(props, array, max) { + const items = []; + + for (let i = 0; i < array.length && i < max; i++) { + const config = { + mode: MODE.TINY, + delim: i == array.length - 1 ? "" : ", " + }; + let item; + + try { + item = ItemRep(_extends({}, props, config, { + object: array[i] + })); + } catch (exc) { + item = ItemRep(_extends({}, props, config, { + object: exc + })); + } + items.push(item); + } + + if (array.length > max) { + items.push(span({ + className: "more-ellipsis", + title: "more…" + }, "…")); + } + + return items; +} + +/** + * Renders array item. Individual values are separated by a comma. + */ +ItemRep.propTypes = { + object: PropTypes.any.isRequired, + delim: PropTypes.string.isRequired, + mode: ModePropType +}; + +function ItemRep(props) { + const { Rep } = __webpack_require__(3647); + + const { object, delim, mode } = props; + return span({}, Rep(_extends({}, props, { + object: object, + mode: mode + })), delim); +} + +function getLength(object) { + return object.length; +} + +function supportsObject(object, noGrip = false) { + return noGrip && (Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]"); +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Exports from this module +module.exports = { + rep: wrapRender(ArrayRep), + supportsObject, + maxLengthMap, + getLength, + ModePropType +}; + +/***/ }), + +/***/ 3650: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); +const { maybeEscapePropertyName, wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); + +const { span } = __webpack_require__(3643); + +/** + * Property for Obj (local JS objects), Grip (remote JS objects) + * and GripMap (remote JS maps and weakmaps) reps. + * It's used to render object properties. + */ +PropRep.propTypes = { + // Property name. + name: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, + // Equal character rendered between property name and value. + equal: PropTypes.string, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + // Normally a PropRep will quote a property name that isn't valid + // when unquoted; but this flag can be used to suppress the + // quoting. + suppressQuotes: PropTypes.bool +}; + +/** + * Function that given a name, a delimiter and an object returns an array + * of React elements representing an object property (e.g. `name: value`) + * + * @param {Object} props + * @return {Array} Array of React elements. + */ +function PropRep(props) { + const Grip = __webpack_require__(3656); + const { Rep } = __webpack_require__(3647); + + let { name, mode, equal, suppressQuotes } = props; + + let key; + // The key can be a simple string, for plain objects, + // or another object for maps and weakmaps. + if (typeof name === "string") { + if (!suppressQuotes) { + name = maybeEscapePropertyName(name); + } + key = span({ className: "nodeName" }, name); + } else { + key = Rep(_extends({}, props, { + className: "nodeName", + object: name, + mode: mode || MODE.TINY, + defaultRep: Grip + })); + } + + return [key, span({ + className: "objectEqual" + }, equal), Rep(_extends({}, props))]; +} + +// Exports from this module +module.exports = wrapRender(PropRep); + +/***/ }), + +/***/ 3655: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { MODE } = __webpack_require__(3645); +const { REPS, getRep } = __webpack_require__(3647); +const ObjectInspector = __webpack_require__(3695); +const ObjectInspectorUtils = __webpack_require__(3657); + +const { + parseURLEncodedText, + parseURLParams, + maybeEscapePropertyName, + getGripPreviewItems +} = __webpack_require__(3644); + +module.exports = { + REPS, + getRep, + MODE, + maybeEscapePropertyName, + parseURLEncodedText, + parseURLParams, + getGripPreviewItems, + ObjectInspector, + ObjectInspectorUtils +}; + +/***/ }), + +/***/ 3656: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Dependencies +const { interleave, isGrip, wrapRender } = __webpack_require__(3644); +const PropRep = __webpack_require__(3650); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders generic grip. Grip is client representation + * of remote JS object and is used as an input object + * for this rep component. + */ +GripRep.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + isInterestingProp: PropTypes.func, + title: PropTypes.string, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + noGrip: PropTypes.bool +}; + +const DEFAULT_TITLE = "Object"; + +function GripRep(props) { + const { mode = MODE.SHORT, object } = props; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-object" + }; + + if (mode === MODE.TINY) { + const propertiesLength = getPropertiesLength(object); + + const tinyModeItems = []; + if (getTitle(props, object) !== DEFAULT_TITLE) { + tinyModeItems.push(getTitleElement(props, object)); + } else { + tinyModeItems.push(span({ + className: "objectLeftBrace" + }, "{"), propertiesLength > 0 ? span({ + key: "more", + className: "more-ellipsis", + title: "more…" + }, "…") : null, span({ + className: "objectRightBrace" + }, "}")); + } + + return span(config, ...tinyModeItems); + } + + const propsArray = safePropIterator(props, object, maxLengthMap.get(mode)); + + return span(config, getTitleElement(props, object), span({ + className: "objectLeftBrace" + }, " { "), ...interleave(propsArray, ", "), span({ + className: "objectRightBrace" + }, " }")); +} + +function getTitleElement(props, object) { + return span({ + className: "objectTitle" + }, getTitle(props, object)); +} + +function getTitle(props, object) { + return props.title || object.class || DEFAULT_TITLE; +} + +function getPropertiesLength(object) { + let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength; + + if (object.preview && object.preview.safeGetterValues) { + propertiesLength += Object.keys(object.preview.safeGetterValues).length; + } + + if (object.preview && object.preview.ownSymbols) { + propertiesLength += object.preview.ownSymbolsLength; + } + + return propertiesLength; +} + +function safePropIterator(props, object, max) { + max = typeof max === "undefined" ? maxLengthMap.get(MODE.SHORT) : max; + try { + return propIterator(props, object, max); + } catch (err) { + console.error(err); + } + return []; +} + +function propIterator(props, object, max) { + if (object.preview && Object.keys(object.preview).includes("wrappedValue")) { + const { Rep } = __webpack_require__(3647); + + return [Rep({ + object: object.preview.wrappedValue, + mode: props.mode || MODE.TINY, + defaultRep: Grip + })]; + } + + // Property filter. Show only interesting properties to the user. + const isInterestingProp = props.isInterestingProp || ((type, value) => { + return type == "boolean" || type == "number" || type == "string" && value.length != 0; + }); + + let properties = object.preview ? object.preview.ownProperties || {} : {}; + + const propertiesLength = getPropertiesLength(object); + + if (object.preview && object.preview.safeGetterValues) { + properties = _extends({}, properties, object.preview.safeGetterValues); + } + + let indexes = getPropIndexes(properties, max, isInterestingProp); + if (indexes.length < max && indexes.length < propertiesLength) { + // There are not enough props yet. + // Then add uninteresting props to display them. + indexes = indexes.concat(getPropIndexes(properties, max - indexes.length, (t, value, name) => { + return !isInterestingProp(t, value, name); + })); + } + + // The server synthesizes some property names for a Proxy, like + // and ; we don't want to quote these because, + // as synthetic properties, they appear more natural when + // unquoted. + const suppressQuotes = object.class === "Proxy"; + const propsArray = getProps(props, properties, indexes, suppressQuotes); + + // Show symbols. + if (object.preview && object.preview.ownSymbols) { + const { ownSymbols } = object.preview; + const length = max - indexes.length; + + const symbolsProps = ownSymbols.slice(0, length).map(symbolItem => { + return PropRep(_extends({}, props, { + mode: MODE.TINY, + name: symbolItem, + object: symbolItem.descriptor.value, + equal: ": ", + defaultRep: Grip, + title: null, + suppressQuotes + })); + }); + + propsArray.push(...symbolsProps); + } + + if (Object.keys(properties).length > max || propertiesLength > max || + // When the object has non-enumerable properties, we don't have them in the + // packet, but we might want to show there's something in the object. + propertiesLength > propsArray.length) { + // There are some undisplayed props. Then display "more...". + propsArray.push(span({ + key: "more", + className: "more-ellipsis", + title: "more…" + }, "…")); + } + + return propsArray; +} + +/** + * Get props ordered by index. + * + * @param {Object} componentProps Grip Component props. + * @param {Object} properties Properties of the object the Grip describes. + * @param {Array} indexes Indexes of properties. + * @param {Boolean} suppressQuotes true if we should suppress quotes + * on property names. + * @return {Array} Props. + */ +function getProps(componentProps, properties, indexes, suppressQuotes) { + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + const propertiesKeys = Object.keys(properties); + return indexes.map(i => { + const name = propertiesKeys[i]; + const value = getPropValue(properties[name]); + + return PropRep(_extends({}, componentProps, { + mode: MODE.TINY, + name, + object: value, + equal: ": ", + defaultRep: Grip, + title: null, + suppressQuotes + })); + }); +} + +/** + * Get the indexes of props in the object. + * + * @param {Object} properties Props object. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the props you want. + * @return {Array} Indexes of interesting props in the object. + */ +function getPropIndexes(properties, max, filter) { + const indexes = []; + + try { + let i = 0; + for (const name in properties) { + if (indexes.length >= max) { + return indexes; + } + + // Type is specified in grip's "class" field and for primitive + // values use typeof. + const value = getPropValue(properties[name]); + let type = value.class || typeof value; + type = type.toLowerCase(); + + if (filter(type, value, name)) { + indexes.push(i); + } + i++; + } + } catch (err) { + console.error(err); + } + return indexes; +} + +/** + * Get the actual value of a property. + * + * @param {Object} property + * @return {Object} Value of the property. + */ +function getPropValue(property) { + let value = property; + if (typeof property === "object") { + const keys = Object.keys(property); + if (keys.includes("value")) { + value = property.value; + } else if (keys.includes("getterValue")) { + value = property.getterValue; + } + } + return value; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + if (object.class === "DeadObject") { + return true; + } + + return object.preview ? typeof object.preview.ownProperties !== "undefined" : typeof object.ownPropertyLength !== "undefined"; +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Grip is used in propIterator and has to be defined here. +const Grip = { + rep: wrapRender(GripRep), + supportsObject, + maxLengthMap +}; + +// Exports from this module +module.exports = Grip; + +/***/ }), + +/***/ 3657: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const client = __webpack_require__(3665); +const loadProperties = __webpack_require__(3666); +const node = __webpack_require__(3667); +const { nodeIsError, nodeIsPrimitive } = node; +const selection = __webpack_require__(3698); + +const { MODE } = __webpack_require__(3645); +const { + REPS: { Rep, Grip } +} = __webpack_require__(3647); + + +function shouldRenderRootsInReps(roots) { + if (roots.length > 1) { + return false; + } + + const root = roots[0]; + const name = root && root.name; + return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root)); +} + +function renderRep(item, props) { + return Rep(_extends({}, props, { + object: node.getValue(item), + mode: props.mode || MODE.TINY, + defaultRep: Grip + })); +} + +module.exports = { + client, + loadProperties, + node, + renderRep, + selection, + shouldRenderRootsInReps +}; + +/***/ }), + +/***/ 3658: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { getGripType, isGrip, cropString, wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +const IGNORED_SOURCE_URLS = ["debugger eval code"]; + +/** + * This component represents a template for Function objects. + */ +FunctionRep.propTypes = { + object: PropTypes.object.isRequired, + parameterNames: PropTypes.array, + onViewSourceInDebugger: PropTypes.func +}; + +function FunctionRep(props) { + const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props; + + let jumpToDefinitionButton; + if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) { + jumpToDefinitionButton = dom.button({ + className: "jump-definition", + draggable: false, + title: "Jump to definition", + onClick: e => { + // Stop the event propagation so we don't trigger ObjectInspector + // expand/collapse. + e.stopPropagation(); + if (recordTelemetryEvent) { + recordTelemetryEvent("jump_to_definition"); + } + onViewSourceInDebugger(grip.location); + } + }); + } + + return span({ + "data-link-actor-id": grip.actor, + className: "objectBox objectBox-function", + // Set dir="ltr" to prevent function parentheses from + // appearing in the wrong direction + dir: "ltr" + }, getTitle(grip, props), getFunctionName(grip, props), "(", ...renderParams(props), ")", jumpToDefinitionButton); +} + +function getTitle(grip, props) { + const { mode } = props; + + if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) { + return null; + } + + let title = mode === MODE.TINY ? "" : "function "; + + if (grip.isGenerator) { + title = mode === MODE.TINY ? "* " : "function* "; + } + + if (grip.isAsync) { + title = `${"async" + " "}${title}`; + } + + return span({ + className: "objectTitle" + }, title); +} + +/** + * Returns a ReactElement representing the function name. + * + * @param {Object} grip : Function grip + * @param {Object} props: Function rep props + */ +function getFunctionName(grip, props = {}) { + let { functionName } = props; + let name; + + if (functionName) { + const end = functionName.length - 1; + functionName = functionName.startsWith('"') && functionName.endsWith('"') ? functionName.substring(1, end) : functionName; + } + + if (grip.displayName != undefined && functionName != undefined && grip.displayName != functionName) { + name = `${functionName}:${grip.displayName}`; + } else { + name = cleanFunctionName(grip.userDisplayName || grip.displayName || grip.name || props.functionName || ""); + } + + return cropString(name, 100); +} + +const objectProperty = /([\w\d]+)$/; +const arrayProperty = /\[(.*?)\]$/; +const functionProperty = /([\w\d]+)[\/\.<]*?$/; +const annonymousProperty = /([\w\d]+)\(\^\)$/; + +/** + * Decodes an anonymous naming scheme that + * spider monkey implements based on "Naming Anonymous JavaScript Functions" + * http://johnjbarton.github.io/nonymous/index.html + * + * @param {String} name : Function name to clean up + * @returns String + */ +function cleanFunctionName(name) { + for (const reg of [objectProperty, arrayProperty, functionProperty, annonymousProperty]) { + const match = reg.exec(name); + if (match) { + return match[1]; + } + } + + return name; +} + +function renderParams(props) { + const { parameterNames = [] } = props; + + return parameterNames.filter(param => param).reduce((res, param, index, arr) => { + res.push(span({ className: "param" }, param)); + if (index < arr.length - 1) { + res.push(span({ className: "delimiter" }, ", ")); + } + return res; + }, []); +} + +// Registration +function supportsObject(grip, noGrip = false) { + const type = getGripType(grip, noGrip); + if (noGrip === true || !isGrip(grip)) { + return type == "function"; + } + + return type == "Function"; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(FunctionRep), + supportsObject, + cleanFunctionName, + // exported for testing purpose. + getFunctionName +}; + +/***/ }), + +/***/ 3659: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +module.exports = { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12, + + // DocumentPosition + DOCUMENT_POSITION_DISCONNECTED: 0x01, + DOCUMENT_POSITION_PRECEDING: 0x02, + DOCUMENT_POSITION_FOLLOWING: 0x04, + DOCUMENT_POSITION_CONTAINS: 0x08, + DOCUMENT_POSITION_CONTAINED_BY: 0x10, + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20 +}; + +/***/ }), + +/***/ 3660: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); +// Utils +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const { cleanFunctionName } = __webpack_require__(3658); +const { isLongString } = __webpack_require__(3648); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; +const IGNORED_SOURCE_URLS = ["debugger eval code"]; + +/** + * Renders Error objects. + */ +ErrorRep.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])) +}; + +function ErrorRep(props) { + const object = props.object; + const preview = object.preview; + + let name; + if (preview && preview.name && preview.kind) { + switch (preview.kind) { + case "Error": + name = preview.name; + break; + case "DOMException": + name = preview.kind; + break; + default: + throw new Error("Unknown preview kind for the Error rep."); + } + } else { + name = "Error"; + } + + const content = []; + + if (props.mode === MODE.TINY) { + content.push(name); + } else { + content.push(`${name}: "${preview.message}"`); + } + + if (preview.stack && props.mode !== MODE.TINY) { + content.push("\n", getStacktraceElements(props, preview)); + } + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox-stackTrace" + }, content); +} + +/** + * Returns a React element reprensenting the Error stacktrace, i.e. + * transform error.stack from: + * + * semicolon@debugger eval code:1:109 + * jkl@debugger eval code:1:63 + * asdf@debugger eval code:1:28 + * @debugger eval code:1:227 + * + * Into a column layout: + * + * semicolon (:8:10) + * jkl (:5:10) + * asdf (:2:10) + * (:11:1) + */ +function getStacktraceElements(props, preview) { + const stack = []; + if (!preview.stack) { + return stack; + } + + const isStacktraceALongString = isLongString(preview.stack); + const stackString = isStacktraceALongString ? preview.stack.initial : preview.stack; + + stackString.split("\n").forEach((frame, index, frames) => { + if (!frame) { + // Skip any blank lines + return; + } + + // If the stacktrace is a longString, don't include the last frame in the + // array, since it is certainly incomplete. + // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833 + // is fixed. + if (isStacktraceALongString && index === frames.length - 1) { + return; + } + + let functionName; + let location; + + // Given the input: "functionName@scriptLocation:2:100" + // Result: [ + // "functionName@scriptLocation:2:100", + // "functionName", + // "scriptLocation:2:100" + // ] + const result = frame.match(/^(.*)@(.*)$/); + if (result && result.length === 3) { + functionName = result[1]; + + // If the resource was loaded by base-loader.js, the location looks like: + // resource://devtools/shared/base-loader.js -> resource://path/to/file.js . + // What's needed is only the last part after " -> ". + location = result[2].split(" -> ").pop(); + } + + if (!functionName) { + functionName = ""; + } + + let onLocationClick; + // Given the input: "scriptLocation:2:100" + // Result: + // ["scriptLocation:2:100", "scriptLocation", "2", "100"] + const locationParts = location.match(/^(.*):(\d+):(\d+)$/); + + if (props.onViewSourceInDebugger && location && locationParts && !IGNORED_SOURCE_URLS.includes(locationParts[1])) { + const [, url, line, column] = locationParts; + onLocationClick = e => { + // Don't trigger ObjectInspector expand/collapse. + e.stopPropagation(); + props.onViewSourceInDebugger({ + url, + line: Number(line), + column: Number(column) + }); + }; + } + + stack.push("\t", span({ + key: `fn${index}`, + className: "objectBox-stackTrace-fn" + }, cleanFunctionName(functionName)), " ", span({ + key: `location${index}`, + className: "objectBox-stackTrace-location", + onClick: onLocationClick, + title: onLocationClick ? `View source in debugger → ${location}` : undefined + }, location), "\n"); + }); + + return span({ + key: "stack", + className: "objectBox-stackTrace-grid" + }, stack); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ErrorRep), + supportsObject +}; + +/***/ }), + +/***/ 3661: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); + +const { lengthBubble } = __webpack_require__(3662); +const { + interleave, + getGripType, + isGrip, + wrapRender, + ellipsisElement +} = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; +const { ModePropType } = __webpack_require__(3649); +const DEFAULT_TITLE = "Array"; + +/** + * Renders an array. The array is enclosed by left and right bracket + * and the max number of rendered items depends on the current mode. + */ +GripArray.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: ModePropType, + provider: PropTypes.object, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function GripArray(props) { + const { object, mode = MODE.SHORT } = props; + + let brackets; + const needSpace = function (space) { + return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" }; + }; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-array" + }; + + const title = getTitle(props, object); + + if (mode === MODE.TINY) { + const isEmpty = getLength(object) === 0; + + // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets). + if (!isEmpty && object.class !== "Array") { + return span(config, title); + } + + brackets = needSpace(false); + return span(config, title, span({ + className: "arrayLeftBracket" + }, brackets.left), isEmpty ? null : ellipsisElement, span({ + className: "arrayRightBracket" + }, brackets.right)); + } + + const max = maxLengthMap.get(mode); + const items = arrayIterator(props, object, max); + brackets = needSpace(items.length > 0); + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox objectBox-array" + }, title, span({ + className: "arrayLeftBracket" + }, brackets.left), ...interleave(items, ", "), span({ + className: "arrayRightBracket" + }, brackets.right), span({ + className: "arrayProperties", + role: "group" + })); +} + +function getLength(grip) { + if (!grip.preview) { + return 0; + } + + return grip.preview.length || grip.preview.childNodesLength || 0; +} + +function getTitle(props, object) { + const objectLength = getLength(object); + const isEmpty = objectLength === 0; + + let title = props.title || object.class || DEFAULT_TITLE; + + const length = lengthBubble({ + object, + mode: props.mode, + maxLengthMap, + getLength + }); + + if (props.mode === MODE.TINY) { + if (isEmpty) { + if (object.class === DEFAULT_TITLE) { + return null; + } + + return span({ className: "objectTitle" }, `${title} `); + } + + let trailingSpace; + if (object.class === DEFAULT_TITLE) { + title = null; + trailingSpace = " "; + } + + return span({ className: "objectTitle" }, title, length, trailingSpace); + } + + return span({ className: "objectTitle" }, title, length, " "); +} + +function getPreviewItems(grip) { + if (!grip.preview) { + return null; + } + + return grip.preview.items || grip.preview.childNodes || []; +} + +function arrayIterator(props, grip, max) { + const { Rep } = __webpack_require__(3647); + + let items = []; + const gripLength = getLength(grip); + + if (!gripLength) { + return items; + } + + const previewItems = getPreviewItems(grip); + const provider = props.provider; + + let emptySlots = 0; + let foldedEmptySlots = 0; + items = previewItems.reduce((res, itemGrip) => { + if (res.length >= max) { + return res; + } + + let object; + try { + if (!provider && itemGrip === null) { + emptySlots++; + return res; + } + + object = provider ? provider.getValue(itemGrip) : itemGrip; + } catch (exc) { + object = exc; + } + + if (emptySlots > 0) { + res.push(getEmptySlotsElement(emptySlots)); + foldedEmptySlots = foldedEmptySlots + emptySlots - 1; + emptySlots = 0; + } + + if (res.length < max) { + res.push(Rep(_extends({}, props, { + object, + mode: MODE.TINY, + // Do not propagate title to array items reps + title: undefined + }))); + } + + return res; + }, []); + + // Handle trailing empty slots if there are some. + if (items.length < max && emptySlots > 0) { + items.push(getEmptySlotsElement(emptySlots)); + foldedEmptySlots = foldedEmptySlots + emptySlots - 1; + } + + const itemsShown = items.length + foldedEmptySlots; + if (gripLength > itemsShown) { + items.push(ellipsisElement); + } + + return items; +} + +function getEmptySlotsElement(number) { + // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141 + return `<${number} empty slot${number > 1 ? "s" : ""}>`; +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && (grip.preview.kind == "ArrayLike" || getGripType(grip, noGrip) === "DocumentFragment"); +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Exports from this module +module.exports = { + rep: wrapRender(GripArray), + supportsObject, + maxLengthMap, + getLength +}; + +/***/ }), + +/***/ 3662: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const PropTypes = __webpack_require__(3642); + +const { wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); +const { ModePropType } = __webpack_require__(3649); + +const dom = __webpack_require__(3643); +const { span } = dom; + +GripLengthBubble.propTypes = { + object: PropTypes.object.isRequired, + maxLengthMap: PropTypes.instanceOf(Map).isRequired, + getLength: PropTypes.func.isRequired, + mode: ModePropType, + visibilityThreshold: PropTypes.number +}; + +function GripLengthBubble(props) { + const { + object, + mode = MODE.SHORT, + visibilityThreshold = 2, + maxLengthMap, + getLength, + showZeroLength = false + } = props; + + const length = getLength(object); + const isEmpty = length === 0; + const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold; + if (isEmpty && !showZeroLength || isObvious) { + return ""; + } + + return span({ + className: "objectLengthBubble" + }, `(${length})`); +} + +module.exports = { + lengthBubble: wrapRender(GripLengthBubble) +}; + +/***/ }), + +/***/ 3663: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies + +const { lengthBubble } = __webpack_require__(3662); +const PropTypes = __webpack_require__(3642); +const { + interleave, + isGrip, + wrapRender, + ellipsisElement +} = __webpack_require__(3644); +const PropRep = __webpack_require__(3650); +const { MODE } = __webpack_require__(3645); +const { ModePropType } = __webpack_require__(3649); + +const { span } = __webpack_require__(3643); + +/** + * Renders an map. A map is represented by a list of its + * entries enclosed in curly brackets. + */ +GripMap.propTypes = { + object: PropTypes.object, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: ModePropType, + isInterestingEntry: PropTypes.func, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + title: PropTypes.string +}; + +function GripMap(props) { + const { mode, object } = props; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-object" + }; + + const title = getTitle(props, object); + const isEmpty = getLength(object) === 0; + + if (isEmpty || mode === MODE.TINY) { + return span(config, title); + } + + const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode)); + + return span(config, title, span({ + className: "objectLeftBrace" + }, " { "), ...interleave(propsArray, ", "), span({ + className: "objectRightBrace" + }, " }")); +} + +function getTitle(props, object) { + const title = props.title || (object && object.class ? object.class : "Map"); + return span({ + className: "objectTitle" + }, title, lengthBubble({ + object, + mode: props.mode, + maxLengthMap, + getLength, + showZeroLength: true + })); +} + +function safeEntriesIterator(props, object, max) { + max = typeof max === "undefined" ? 3 : max; + try { + return entriesIterator(props, object, max); + } catch (err) { + console.error(err); + } + return []; +} + +function entriesIterator(props, object, max) { + // Entry filter. Show only interesting entries to the user. + const isInterestingEntry = props.isInterestingEntry || ((type, value) => { + return type == "boolean" || type == "number" || type == "string" && value.length != 0; + }); + + const mapEntries = object.preview && object.preview.entries ? object.preview.entries : []; + + let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry); + if (indexes.length < max && indexes.length < mapEntries.length) { + // There are not enough entries yet, so we add uninteresting entries. + indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => { + return !isInterestingEntry(t, value, name); + })); + } + + const entries = getEntries(props, mapEntries, indexes); + if (entries.length < getLength(object)) { + // There are some undisplayed entries. Then display "…". + entries.push(ellipsisElement); + } + + return entries; +} + +/** + * Get entries ordered by index. + * + * @param {Object} props Component props. + * @param {Array} entries Entries array. + * @param {Array} indexes Indexes of entries. + * @return {Array} Array of PropRep. + */ +function getEntries(props, entries, indexes) { + const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props; + + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + return indexes.map((index, i) => { + const [key, entryValue] = entries[index]; + const value = entryValue.value !== undefined ? entryValue.value : entryValue; + + return PropRep({ + name: key, + equal: " \u2192 ", + object: value, + mode: MODE.TINY, + onDOMNodeMouseOver, + onDOMNodeMouseOut, + onInspectIconClick + }); + }); +} + +/** + * Get the indexes of entries in the map. + * + * @param {Array} entries Entries array. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the entry you want. + * @return {Array} Indexes of filtered entries in the map. + */ +function getEntriesIndexes(entries, max, filter) { + return entries.reduce((indexes, [key, entry], i) => { + if (indexes.length < max) { + const value = entry && entry.value !== undefined ? entry.value : entry; + // Type is specified in grip's "class" field and for primitive + // values use typeof. + const type = (value && value.class ? value.class : typeof value).toLowerCase(); + + if (filter(type, value, key)) { + indexes.push(i); + } + } + + return indexes; + }, []); +} + +function getLength(grip) { + return grip.preview.size || 0; +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + return grip.preview && grip.preview.kind == "MapLike"; +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Exports from this module +module.exports = { + rep: wrapRender(GripMap), + supportsObject, + maxLengthMap, + getLength +}; + +/***/ }), + +/***/ 3664: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); +// Shortcuts +const dom = __webpack_require__(3643); +const { span } = dom; +const { wrapRender } = __webpack_require__(3644); +const PropRep = __webpack_require__(3650); +const { MODE } = __webpack_require__(3645); +/** + * Renders an map entry. A map entry is represented by its key, + * a column and its value. + */ +GripMapEntry.propTypes = { + object: PropTypes.object, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function GripMapEntry(props) { + const { object } = props; + + const { key, value } = object.preview; + + return span({ + className: "objectBox objectBox-map-entry" + }, PropRep(_extends({}, props, { + name: key, + object: value, + equal: " \u2192 ", + title: null, + suppressQuotes: false + }))); +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true) { + return false; + } + return grip && (grip.type === "mapEntry" || grip.type === "storageEntry") && grip.preview; +} + +function createGripMapEntry(key, value) { + return { + type: "mapEntry", + preview: { + key, + value + } + }; +} + +// Exports from this module +module.exports = { + rep: wrapRender(GripMapEntry), + createGripMapEntry, + supportsObject +}; + +/***/ }), + +/***/ 3665: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const { getValue, nodeHasFullText } = __webpack_require__(3667); /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +async function enumIndexedProperties(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumProperties({ + ignoreNonIndexedProperties: true + }); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumIndexedProperties", e); + return {}; + } +} + +async function enumNonIndexedProperties(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumProperties({ + ignoreIndexedProperties: true + }); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumNonIndexedProperties", e); + return {}; + } +} + +async function enumEntries(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumEntries(); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumEntries", e); + return {}; + } +} + +async function enumSymbols(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumSymbols(); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumSymbols", e); + return {}; + } +} + +async function getPrototype(objectClient) { + if (typeof objectClient.getPrototype !== "function") { + console.error("objectClient.getPrototype is not a function"); + return Promise.resolve({}); + } + return objectClient.getPrototype(); +} + +async function getFullText(longStringClient, item) { + const { initial, fullText, length } = getValue(item); + + // Return fullText property if it exists so that it can be added to the + // loadedProperties map. + if (nodeHasFullText(item)) { + return Promise.resolve({ fullText }); + } + + return new Promise((resolve, reject) => { + longStringClient.substring(initial.length, length, response => { + if (response.error) { + console.error("LongStringClient.substring", `${response.error}: ${response.message}`); + reject({}); + return; + } + + resolve({ + fullText: initial + response.substring + }); + }); + }); +} + +function iteratorSlice(iterator, start, end) { + start = start || 0; + const count = end ? end - start + 1 : iterator.count; + + if (count === 0) { + return Promise.resolve({}); + } + return iterator.slice(start, count); +} + +module.exports = { + enumEntries, + enumIndexedProperties, + enumNonIndexedProperties, + enumSymbols, + getPrototype, + getFullText +}; + +/***/ }), + +/***/ 3666: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { + enumEntries, + enumIndexedProperties, + enumNonIndexedProperties, + getPrototype, + enumSymbols, + getFullText +} = __webpack_require__(3665); + +const { + getClosestGripNode, + getClosestNonBucketNode, + getValue, + nodeHasAccessors, + nodeHasAllEntriesInPreview, + nodeHasProperties, + nodeIsBucket, + nodeIsDefaultProperties, + nodeIsEntries, + nodeIsMapEntry, + nodeIsPrimitive, + nodeIsProxy, + nodeNeedsNumericalBuckets, + nodeIsLongString +} = __webpack_require__(3667); + +function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : []; + + const promises = []; + let objectClient; + const getObjectClient = () => objectClient || createObjectClient(value); + + if (shouldLoadItemIndexedProperties(item, loadedProperties)) { + promises.push(enumIndexedProperties(getObjectClient(), start, end)); + } + + if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) { + promises.push(enumNonIndexedProperties(getObjectClient(), start, end)); + } + + if (shouldLoadItemEntries(item, loadedProperties)) { + promises.push(enumEntries(getObjectClient(), start, end)); + } + + if (shouldLoadItemPrototype(item, loadedProperties)) { + promises.push(getPrototype(getObjectClient())); + } + + if (shouldLoadItemSymbols(item, loadedProperties)) { + promises.push(enumSymbols(getObjectClient(), start, end)); + } + + if (shouldLoadItemFullText(item, loadedProperties)) { + promises.push(getFullText(createLongStringClient(value), item)); + } + + return Promise.all(promises).then(mergeResponses); +} + +function mergeResponses(responses) { + const data = {}; + + for (const response of responses) { + if (response.hasOwnProperty("ownProperties")) { + data.ownProperties = _extends({}, data.ownProperties, response.ownProperties); + } + + if (response.ownSymbols && response.ownSymbols.length > 0) { + data.ownSymbols = response.ownSymbols; + } + + if (response.prototype) { + data.prototype = response.prototype; + } + + if (response.fullText) { + data.fullText = response.fullText; + } + } + + return data; +} + +function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeNeedsNumericalBuckets(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && + // The data is loaded when expanding the window node. + !nodeIsDefaultProperties(item); +} + +function shouldLoadItemNonIndexedProperties(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && !nodeIsBucket(item) && + // The data is loaded when expanding the window node. + !nodeIsDefaultProperties(item); +} + +function shouldLoadItemEntries(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeIsEntries(getClosestNonBucketNode(item)) && !nodeHasAllEntriesInPreview(gripItem) && !loadedProperties.has(item.path) && !nodeNeedsNumericalBuckets(item); +} + +function shouldLoadItemPrototype(item, loadedProperties = new Map()) { + const value = getValue(item); + + return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item); +} + +function shouldLoadItemSymbols(item, loadedProperties = new Map()) { + const value = getValue(item); + + return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item); +} + +function shouldLoadItemFullText(item, loadedProperties = new Map()) { + return !loadedProperties.has(item.path) && nodeIsLongString(item); +} + +module.exports = { + loadItemProperties, + mergeResponses, + shouldLoadItemEntries, + shouldLoadItemIndexedProperties, + shouldLoadItemNonIndexedProperties, + shouldLoadItemPrototype, + shouldLoadItemSymbols, + shouldLoadItemFullText +}; + +/***/ }), + +/***/ 3667: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { maybeEscapePropertyName } = __webpack_require__(3644); +const ArrayRep = __webpack_require__(3649); +const GripArrayRep = __webpack_require__(3661); +const GripMap = __webpack_require__(3663); +const GripMapEntryRep = __webpack_require__(3664); +const ErrorRep = __webpack_require__(3660); +const { isLongString } = __webpack_require__(3648); + +const MAX_NUMERICAL_PROPERTIES = 100; + +const NODE_TYPES = { + BUCKET: Symbol("[n…m]"), + DEFAULT_PROPERTIES: Symbol(""), + ENTRIES: Symbol(""), + GET: Symbol(""), + GRIP: Symbol("GRIP"), + MAP_ENTRY_KEY: Symbol(""), + MAP_ENTRY_VALUE: Symbol(""), + PROMISE_REASON: Symbol(""), + PROMISE_STATE: Symbol(""), + PROMISE_VALUE: Symbol(""), + PROXY_HANDLER: Symbol(""), + PROXY_TARGET: Symbol(""), + SET: Symbol(""), + PROTOTYPE: Symbol(""), + BLOCK: Symbol("☲") +}; + +let WINDOW_PROPERTIES = {}; + +if (typeof window === "object") { + WINDOW_PROPERTIES = Object.getOwnPropertyNames(window); +} + +function getType(item) { + return item.type; +} + +function getValue(item) { + if (nodeHasValue(item)) { + return item.contents.value; + } + + if (nodeHasGetterValue(item)) { + return item.contents.getterValue; + } + + if (nodeHasAccessors(item)) { + return item.contents; + } + + return undefined; +} + +function nodeIsBucket(item) { + return getType(item) === NODE_TYPES.BUCKET; +} + +function nodeIsEntries(item) { + return getType(item) === NODE_TYPES.ENTRIES; +} + +function nodeIsMapEntry(item) { + return GripMapEntryRep.supportsObject(getValue(item)); +} + +function nodeHasChildren(item) { + return Array.isArray(item.contents); +} + +function nodeHasValue(item) { + return item && item.contents && item.contents.hasOwnProperty("value"); +} + +function nodeHasGetterValue(item) { + return item && item.contents && item.contents.hasOwnProperty("getterValue"); +} + +function nodeIsObject(item) { + const value = getValue(item); + return value && value.type === "object"; +} + +function nodeIsArrayLike(item) { + const value = getValue(item); + return GripArrayRep.supportsObject(value) || ArrayRep.supportsObject(value); +} + +function nodeIsFunction(item) { + const value = getValue(item); + return value && value.class === "Function"; +} + +function nodeIsOptimizedOut(item) { + const value = getValue(item); + return !nodeHasChildren(item) && value && value.optimizedOut; +} + +function nodeIsUninitializedBinding(item) { + const value = getValue(item); + return value && value.uninitialized; +} + +// Used to check if an item represents a binding that exists in a sourcemap's +// original file content, but does not match up with a binding found in the +// generated code. +function nodeIsUnmappedBinding(item) { + const value = getValue(item); + return value && value.unmapped; +} + +// Used to check if an item represents a binding that exists in the debugger's +// parser result, but does not match up with a binding returned by the +// debugger server. +function nodeIsUnscopedBinding(item) { + const value = getValue(item); + return value && value.unscoped; +} + +function nodeIsMissingArguments(item) { + const value = getValue(item); + return !nodeHasChildren(item) && value && value.missingArguments; +} + +function nodeHasProperties(item) { + return !nodeHasChildren(item) && nodeIsObject(item); +} + +function nodeIsPrimitive(item) { + return !nodeHasChildren(item) && !nodeHasProperties(item) && !nodeIsEntries(item) && !nodeIsMapEntry(item) && !nodeHasAccessors(item) && !nodeIsBucket(item) && !nodeIsLongString(item); +} + +function nodeIsDefaultProperties(item) { + return getType(item) === NODE_TYPES.DEFAULT_PROPERTIES; +} + +function isDefaultWindowProperty(name) { + return WINDOW_PROPERTIES.includes(name); +} + +function nodeIsPromise(item) { + const value = getValue(item); + if (!value) { + return false; + } + + return value.class == "Promise"; +} + +function nodeIsProxy(item) { + const value = getValue(item); + if (!value) { + return false; + } + + return value.class == "Proxy"; +} + +function nodeIsPrototype(item) { + return getType(item) === NODE_TYPES.PROTOTYPE; +} + +function nodeIsWindow(item) { + const value = getValue(item); + if (!value) { + return false; + } + + return value.class == "Window"; +} + +function nodeIsGetter(item) { + return getType(item) === NODE_TYPES.GET; +} + +function nodeIsSetter(item) { + return getType(item) === NODE_TYPES.SET; +} + +function nodeIsBlock(item) { + return getType(item) === NODE_TYPES.BLOCK; +} + +function nodeIsError(item) { + return ErrorRep.supportsObject(getValue(item)); +} + +function nodeIsLongString(item) { + return isLongString(getValue(item)); +} + +function nodeHasFullText(item) { + const value = getValue(item); + return nodeIsLongString(item) && value.hasOwnProperty("fullText"); +} + +function nodeHasAccessors(item) { + return !!getNodeGetter(item) || !!getNodeSetter(item); +} + +function nodeSupportsNumericalBucketing(item) { + // We exclude elements with entries since it's the node + // itself that can have buckets. + return nodeIsArrayLike(item) && !nodeHasEntries(item) || nodeIsEntries(item) || nodeIsBucket(item); +} + +function nodeHasEntries(item) { + const value = getValue(item); + if (!value) { + return false; + } + + return value.class === "Map" || value.class === "Set" || value.class === "WeakMap" || value.class === "WeakSet" || value.class === "Storage"; +} + +function nodeHasAllEntriesInPreview(item) { + const { preview } = getValue(item) || {}; + if (!preview) { + return false; + } + + const { entries, items, length, size } = preview; + + if (!entries && !items) { + return false; + } + + return entries ? entries.length === size : items.length === length; +} + +function nodeNeedsNumericalBuckets(item) { + return nodeSupportsNumericalBucketing(item) && getNumericalPropertiesCount(item) > MAX_NUMERICAL_PROPERTIES; +} + +function makeNodesForPromiseProperties(item) { + const { + promiseState: { reason, value, state } + } = getValue(item); + + const properties = []; + + if (state) { + properties.push(createNode({ + parent: item, + name: "", + contents: { value: state }, + type: NODE_TYPES.PROMISE_STATE + })); + } + + if (reason) { + properties.push(createNode({ + parent: item, + name: "", + contents: { value: reason }, + type: NODE_TYPES.PROMISE_REASON + })); + } + + if (value) { + properties.push(createNode({ + parent: item, + name: "", + contents: { value: value }, + type: NODE_TYPES.PROMISE_VALUE + })); + } + + return properties; +} + +function makeNodesForProxyProperties(item) { + const { proxyHandler, proxyTarget } = getValue(item); + + return [createNode({ + parent: item, + name: "", + contents: { value: proxyTarget }, + type: NODE_TYPES.PROXY_TARGET + }), createNode({ + parent: item, + name: "", + contents: { value: proxyHandler }, + type: NODE_TYPES.PROXY_HANDLER + })]; +} + +function makeNodesForEntries(item) { + const nodeName = ""; + const entriesPath = ""; + + if (nodeHasAllEntriesInPreview(item)) { + let entriesNodes = []; + const { preview } = getValue(item); + if (preview.entries) { + entriesNodes = preview.entries.map(([key, value], index) => { + return createNode({ + parent: item, + name: index, + path: `${entriesPath}/${index}`, + contents: { value: GripMapEntryRep.createGripMapEntry(key, value) } + }); + }); + } else if (preview.items) { + entriesNodes = preview.items.map((value, index) => { + return createNode({ + parent: item, + name: index, + path: `${entriesPath}/${index}`, + contents: { value } + }); + }); + } + return createNode({ + parent: item, + name: nodeName, + contents: entriesNodes, + type: NODE_TYPES.ENTRIES + }); + } + return createNode({ + parent: item, + name: nodeName, + contents: null, + type: NODE_TYPES.ENTRIES + }); +} + +function makeNodesForMapEntry(item) { + const nodeValue = getValue(item); + if (!nodeValue || !nodeValue.preview) { + return []; + } + + const { key, value } = nodeValue.preview; + + return [createNode({ + parent: item, + name: "", + contents: { value: key }, + type: NODE_TYPES.MAP_ENTRY_KEY + }), createNode({ + parent: item, + name: "", + contents: { value }, + type: NODE_TYPES.MAP_ENTRY_VALUE + })]; +} + +function getNodeGetter(item) { + return item && item.contents ? item.contents.get : undefined; +} + +function getNodeSetter(item) { + return item && item.contents ? item.contents.set : undefined; +} + +function makeNodesForAccessors(item) { + const accessors = []; + + const getter = getNodeGetter(item); + if (getter && getter.type !== "undefined") { + accessors.push(createNode({ + parent: item, + name: "", + contents: { value: getter }, + type: NODE_TYPES.GET + })); + } + + const setter = getNodeSetter(item); + if (setter && setter.type !== "undefined") { + accessors.push(createNode({ + parent: item, + name: "", + contents: { value: setter }, + type: NODE_TYPES.SET + })); + } + + return accessors; +} + +function sortProperties(properties) { + return properties.sort((a, b) => { + // Sort numbers in ascending order and sort strings lexicographically + const aInt = parseInt(a, 10); + const bInt = parseInt(b, 10); + + if (isNaN(aInt) || isNaN(bInt)) { + return a > b ? 1 : -1; + } + + return aInt - bInt; + }); +} + +function makeNumericalBuckets(parent) { + const numProperties = getNumericalPropertiesCount(parent); + + // We want to have at most a hundred slices. + const bucketSize = 10 ** Math.max(2, Math.ceil(Math.log10(numProperties)) - 2); + const numBuckets = Math.ceil(numProperties / bucketSize); + + const buckets = []; + for (let i = 1; i <= numBuckets; i++) { + const minKey = (i - 1) * bucketSize; + const maxKey = Math.min(i * bucketSize - 1, numProperties - 1); + const startIndex = nodeIsBucket(parent) ? parent.meta.startIndex : 0; + const minIndex = startIndex + minKey; + const maxIndex = startIndex + maxKey; + const bucketName = `[${minIndex}…${maxIndex}]`; + + buckets.push(createNode({ + parent, + name: bucketName, + contents: null, + type: NODE_TYPES.BUCKET, + meta: { + startIndex: minIndex, + endIndex: maxIndex + } + })); + } + return buckets; +} + +function makeDefaultPropsBucket(propertiesNames, parent, ownProperties) { + const userPropertiesNames = []; + const defaultProperties = []; + + propertiesNames.forEach(name => { + if (isDefaultWindowProperty(name)) { + defaultProperties.push(name); + } else { + userPropertiesNames.push(name); + } + }); + + const nodes = makeNodesForOwnProps(userPropertiesNames, parent, ownProperties); + + if (defaultProperties.length > 0) { + const defaultPropertiesNode = createNode({ + parent, + name: "", + contents: null, + type: NODE_TYPES.DEFAULT_PROPERTIES + }); + + const defaultNodes = defaultProperties.map((name, index) => createNode({ + parent: defaultPropertiesNode, + name: maybeEscapePropertyName(name), + path: `${index}/${name}`, + contents: ownProperties[name] + })); + nodes.push(setNodeChildren(defaultPropertiesNode, defaultNodes)); + } + return nodes; +} + +function makeNodesForOwnProps(propertiesNames, parent, ownProperties) { + return propertiesNames.map(name => createNode({ + parent, + name: maybeEscapePropertyName(name), + contents: ownProperties[name] + })); +} + +function makeNodesForProperties(objProps, parent) { + const { + ownProperties = {}, + ownSymbols, + prototype, + safeGetterValues + } = objProps; + + const parentValue = getValue(parent); + + const allProperties = _extends({}, ownProperties, safeGetterValues); + + // Ignore properties that are neither non-concrete nor getters/setters. + const propertiesNames = sortProperties(Object.keys(allProperties)).filter(name => { + if (!allProperties[name]) { + return false; + } + + const properties = Object.getOwnPropertyNames(allProperties[name]); + return properties.some(property => ["value", "getterValue", "get", "set"].includes(property)); + }); + + let nodes = []; + if (parentValue && parentValue.class == "Window") { + nodes = makeDefaultPropsBucket(propertiesNames, parent, allProperties); + } else { + nodes = makeNodesForOwnProps(propertiesNames, parent, allProperties); + } + + if (Array.isArray(ownSymbols)) { + ownSymbols.forEach((ownSymbol, index) => { + nodes.push(createNode({ + parent, + name: ownSymbol.name, + path: `symbol-${index}`, + contents: ownSymbol.descriptor || null + })); + }, this); + } + + if (nodeIsPromise(parent)) { + nodes.push(...makeNodesForPromiseProperties(parent)); + } + + if (nodeHasEntries(parent)) { + nodes.push(makeNodesForEntries(parent)); + } + + // Add the prototype if it exists and is not null + if (prototype && prototype.type !== "null") { + nodes.push(makeNodeForPrototype(objProps, parent)); + } + + return nodes; +} + +function setNodeFullText(loadedProps, node) { + if (nodeHasFullText(node) || !nodeIsLongString(node)) { + return node; + } + + const { fullText } = loadedProps; + if (nodeHasValue(node)) { + node.contents.value.fullText = fullText; + } else if (nodeHasGetterValue(node)) { + node.contents.getterValue.fullText = fullText; + } + + return node; +} + +function makeNodeForPrototype(objProps, parent) { + const { prototype } = objProps || {}; + + // Add the prototype if it exists and is not null + if (prototype && prototype.type !== "null") { + return createNode({ + parent, + name: "", + contents: { value: prototype }, + type: NODE_TYPES.PROTOTYPE + }); + } + + return null; +} + +function createNode(options) { + const { + parent, + name, + path, + contents, + type = NODE_TYPES.GRIP, + meta + } = options; + + if (contents === undefined) { + return null; + } + + // The path is important to uniquely identify the item in the entire + // tree. This helps debugging & optimizes React's rendering of large + // lists. The path will be separated by property name, wrapped in a Symbol + // to avoid name clashing, + // i.e. `{ foo: { bar: { baz: 5 }}}` will have a path of Symbol(`foo/bar/baz`) + // for the inner object. + return { + parent, + name, + path: parent ? Symbol(`${getSymbolDescriptor(parent.path)}/${path || name}`) : Symbol(path || name), + contents, + type, + meta + }; +} + +function getSymbolDescriptor(symbol) { + return symbol.toString().replace(/^(Symbol\()(.*)(\))$/, "$2"); +} + +function setNodeChildren(node, children) { + node.contents = children; + return node; +} + +function getChildren(options) { + const { cachedNodes, loadedProperties = new Map(), item } = options; + + const key = item.path; + if (cachedNodes && cachedNodes.has(key)) { + return cachedNodes.get(key); + } + + const loadedProps = loadedProperties.get(key); + const hasLoadedProps = loadedProperties.has(key); + + // Because we are dynamically creating the tree as the user + // expands it (not precalculated tree structure), we cache child + // arrays. This not only helps performance, but is necessary + // because the expanded state depends on instances of nodes + // being the same across renders. If we didn't do this, each + // node would be a new instance every render. + // If the node needs properties, we only add children to + // the cache if the properties are loaded. + const addToCache = children => { + if (cachedNodes) { + cachedNodes.set(item.path, children); + } + return children; + }; + + // Nodes can either have children already, or be an object with + // properties that we need to go and fetch. + if (nodeHasChildren(item)) { + return addToCache(item.contents); + } + + if (nodeHasAccessors(item)) { + return addToCache(makeNodesForAccessors(item)); + } + + if (nodeIsMapEntry(item)) { + return addToCache(makeNodesForMapEntry(item)); + } + + if (nodeIsProxy(item)) { + return addToCache(makeNodesForProxyProperties(item)); + } + + if (nodeIsLongString(item) && hasLoadedProps) { + // Set longString object's fullText to fetched one. + return addToCache(setNodeFullText(loadedProps, item)); + } + + if (nodeNeedsNumericalBuckets(item) && hasLoadedProps) { + // Even if we have numerical buckets, we should have loaded non indexed + // properties. + const bucketNodes = makeNumericalBuckets(item); + return addToCache(bucketNodes.concat(makeNodesForProperties(loadedProps, item))); + } + + if (!nodeIsEntries(item) && !nodeIsBucket(item) && !nodeHasProperties(item)) { + return []; + } + + if (!hasLoadedProps) { + return []; + } + + return addToCache(makeNodesForProperties(loadedProps, item)); +} + +function getParent(item) { + return item.parent; +} + +function getNumericalPropertiesCount(item) { + if (nodeIsBucket(item)) { + return item.meta.endIndex - item.meta.startIndex + 1; + } + + const value = getValue(getClosestGripNode(item)); + if (!value) { + return 0; + } + + if (GripArrayRep.supportsObject(value)) { + return GripArrayRep.getLength(value); + } + + if (GripMap.supportsObject(value)) { + return GripMap.getLength(value); + } + + // TODO: We can also have numerical properties on Objects, but at the + // moment we don't have a way to distinguish them from non-indexed properties, + // as they are all computed in a ownPropertiesLength property. + + return 0; +} + +function getClosestGripNode(item) { + const type = getType(item); + if (type !== NODE_TYPES.BUCKET && type !== NODE_TYPES.DEFAULT_PROPERTIES && type !== NODE_TYPES.ENTRIES) { + return item; + } + + const parent = getParent(item); + if (!parent) { + return null; + } + + return getClosestGripNode(parent); +} + +function getClosestNonBucketNode(item) { + const type = getType(item); + + if (type !== NODE_TYPES.BUCKET) { + return item; + } + + const parent = getParent(item); + if (!parent) { + return null; + } + + return getClosestNonBucketNode(parent); +} + +module.exports = { + createNode, + getChildren, + getClosestGripNode, + getClosestNonBucketNode, + getParent, + getNumericalPropertiesCount, + getValue, + makeNodesForEntries, + makeNodesForPromiseProperties, + makeNodesForProperties, + makeNumericalBuckets, + nodeHasAccessors, + nodeHasAllEntriesInPreview, + nodeHasChildren, + nodeHasEntries, + nodeHasProperties, + nodeIsBlock, + nodeIsBucket, + nodeIsDefaultProperties, + nodeIsEntries, + nodeIsError, + nodeIsLongString, + nodeHasFullText, + nodeIsFunction, + nodeIsGetter, + nodeIsMapEntry, + nodeIsMissingArguments, + nodeIsObject, + nodeIsOptimizedOut, + nodeIsPrimitive, + nodeIsPromise, + nodeIsPrototype, + nodeIsProxy, + nodeIsSetter, + nodeIsUninitializedBinding, + nodeIsUnmappedBinding, + nodeIsUnscopedBinding, + nodeIsWindow, + nodeNeedsNumericalBuckets, + nodeSupportsNumericalBucketing, + setNodeChildren, + sortProperties, + NODE_TYPES +}; + +/***/ }), + +/***/ 3669: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _tree = __webpack_require__(3670); + +var _tree2 = _interopRequireDefault(_tree); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +module.exports = { + Tree: _tree2.default +}; /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/***/ }), + +/***/ 3670: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = __webpack_require__(0); + +var _react2 = _interopRequireDefault(_react); + +var _reactDomFactories = __webpack_require__(3643); + +var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories); + +var _propTypes = __webpack_require__(3642); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const { Component, createFactory } = _react2.default; /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +__webpack_require__(3671); + +// depth +const AUTO_EXPAND_DEPTH = 0; + +/** + * An arrow that displays whether its node is expanded (▼) or collapsed + * (▶). When its node has no children, it is hidden. + */ +class ArrowExpander extends Component { + static get propTypes() { + return { + expanded: _propTypes2.default.bool + }; + } + + shouldComponentUpdate(nextProps, nextState) { + return this.props.expanded !== nextProps.expanded; + } + + render() { + const { expanded } = this.props; + + const classNames = ["arrow"]; + if (expanded) { + classNames.push("expanded"); + } + return _reactDomFactories2.default.img({ + className: classNames.join(" ") + }); + } +} + +const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B"); + +class TreeNode extends Component { + static get propTypes() { + return { + id: _propTypes2.default.any.isRequired, + index: _propTypes2.default.number.isRequired, + depth: _propTypes2.default.number.isRequired, + focused: _propTypes2.default.bool.isRequired, + expanded: _propTypes2.default.bool.isRequired, + item: _propTypes2.default.any.isRequired, + isExpandable: _propTypes2.default.bool.isRequired, + onClick: _propTypes2.default.func, + renderItem: _propTypes2.default.func.isRequired + }; + } + + shouldComponentUpdate(nextProps) { + return this.props.item !== nextProps.item || this.props.focused !== nextProps.focused || this.props.expanded !== nextProps.expanded; + } + + render() { + const { + depth, + id, + item, + focused, + expanded, + renderItem, + isExpandable + } = this.props; + + const arrow = isExpandable ? ArrowExpanderFactory({ + item, + expanded + }) : null; + + let ariaExpanded; + if (this.props.isExpandable) { + ariaExpanded = false; + } + if (this.props.expanded) { + ariaExpanded = true; + } + + const indents = Array.from({ length: depth }).fill(treeIndent); + const items = indents.concat(renderItem(item, depth, focused, arrow, expanded)); + + return _reactDomFactories2.default.div({ + id, + className: `tree-node${focused ? " focused" : ""}`, + onClick: this.props.onClick, + role: "treeitem", + "aria-level": depth + 1, + "aria-expanded": ariaExpanded, + "data-expandable": this.props.isExpandable + }, ...items); + } +} + +const ArrowExpanderFactory = createFactory(ArrowExpander); +const TreeNodeFactory = createFactory(TreeNode); + +/** + * Create a function that calls the given function `fn` only once per animation + * frame. + * + * @param {Function} fn + * @returns {Function} + */ +function oncePerAnimationFrame(fn) { + let animationId = null; + let argsToPass = null; + return function (...args) { + argsToPass = args; + if (animationId !== null) { + return; + } + + animationId = requestAnimationFrame(() => { + fn.call(this, ...argsToPass); + animationId = null; + argsToPass = null; + }); + }; +} + +/** + * A generic tree component. See propTypes for the public API. + * + * This tree component doesn't make any assumptions about the structure of your + * tree data. Whether children are computed on demand, or stored in an array in + * the parent's `_children` property, it doesn't matter. We only require the + * implementation of `getChildren`, `getRoots`, `getParent`, and `isExpanded` + * functions. + * + * This tree component is well tested and reliable. See the tests in ./tests + * and its usage in the performance and memory panels in mozilla-central. + * + * This tree component doesn't make any assumptions about how to render items in + * the tree. You provide a `renderItem` function, and this component will ensure + * that only those items whose parents are expanded and which are visible in the + * viewport are rendered. The `renderItem` function could render the items as a + * "traditional" tree or as rows in a table or anything else. It doesn't + * restrict you to only one certain kind of tree. + * + * The tree comes with basic styling for the indent, the arrow, as well as + * hovered and focused styles which can be override in CSS. + * + * ### Example Usage + * + * Suppose we have some tree data where each item has this form: + * + * { + * id: Number, + * label: String, + * parent: Item or null, + * children: Array of child items, + * expanded: bool, + * } + * + * Here is how we could render that data with this component: + * + * class MyTree extends Component { + * static get propTypes() { + * // The root item of the tree, with the form described above. + * return { + * root: PropTypes.object.isRequired + * }; + * }, + * + * render() { + * return Tree({ + * itemHeight: 20, // px + * + * getRoots: () => [this.props.root], + * + * getParent: item => item.parent, + * getChildren: item => item.children, + * getKey: item => item.id, + * isExpanded: item => item.expanded, + * + * renderItem: (item, depth, isFocused, arrow, isExpanded) => { + * let className = "my-tree-item"; + * if (isFocused) { + * className += " focused"; + * } + * return dom.div({ + * className, + * }, + * arrow, + * // And here is the label for this item. + * dom.span({ className: "my-tree-item-label" }, item.label) + * ); + * }, + * + * onExpand: item => dispatchExpandActionToRedux(item), + * onCollapse: item => dispatchCollapseActionToRedux(item), + * }); + * } + * } + */ +class Tree extends Component { + static get propTypes() { + return { + // Required props + + // A function to get an item's parent, or null if it is a root. + // + // Type: getParent(item: Item) -> Maybe + // + // Example: + // + // // The parent of this item is stored in its `parent` property. + // getParent: item => item.parent + getParent: _propTypes2.default.func.isRequired, + + // A function to get an item's children. + // + // Type: getChildren(item: Item) -> [Item] + // + // Example: + // + // // This item's children are stored in its `children` property. + // getChildren: item => item.children + getChildren: _propTypes2.default.func.isRequired, + + // A function which takes an item and ArrowExpander component instance and + // returns a component, or text, or anything else that React considers + // renderable. + // + // Type: renderItem(item: Item, + // depth: Number, + // isFocused: Boolean, + // arrow: ReactComponent, + // isExpanded: Boolean) -> ReactRenderable + // + // Example: + // + // renderItem: (item, depth, isFocused, arrow, isExpanded) => { + // let className = "my-tree-item"; + // if (isFocused) { + // className += " focused"; + // } + // return dom.div( + // { + // className, + // style: { marginLeft: depth * 10 + "px" } + // }, + // arrow, + // dom.span({ className: "my-tree-item-label" }, item.label) + // ); + // }, + renderItem: _propTypes2.default.func.isRequired, + + // A function which returns the roots of the tree (forest). + // + // Type: getRoots() -> [Item] + // + // Example: + // + // // In this case, we only have one top level, root item. You could + // // return multiple items if you have many top level items in your + // // tree. + // getRoots: () => [this.props.rootOfMyTree] + getRoots: _propTypes2.default.func.isRequired, + + // A function to get a unique key for the given item. This helps speed up + // React's rendering a *TON*. + // + // Type: getKey(item: Item) -> String + // + // Example: + // + // getKey: item => `my-tree-item-${item.uniqueId}` + getKey: _propTypes2.default.func.isRequired, + + // A function to get whether an item is expanded or not. If an item is not + // expanded, then it must be collapsed. + // + // Type: isExpanded(item: Item) -> Boolean + // + // Example: + // + // isExpanded: item => item.expanded, + isExpanded: _propTypes2.default.func.isRequired, + + // Optional props + + // The currently focused item, if any such item exists. + focused: _propTypes2.default.any, + + // Handle when a new item is focused. + onFocus: _propTypes2.default.func, + + // The depth to which we should automatically expand new items. + autoExpandDepth: _propTypes2.default.number, + // Should auto expand all new items or just the new items under the first + // root item. + autoExpandAll: _propTypes2.default.bool, + + // Note: the two properties below are mutually exclusive. Only one of the + // label properties is necessary. + // ID of an element whose textual content serves as an accessible label + // for a tree. + labelledby: _propTypes2.default.string, + // Accessibility label for a tree widget. + label: _propTypes2.default.string, + + // Optional event handlers for when items are expanded or collapsed. + // Useful for dispatching redux events and updating application state, + // maybe lazily loading subtrees from a worker, etc. + // + // Type: + // onExpand(item: Item) + // onCollapse(item: Item) + // + // Example: + // + // onExpand: item => dispatchExpandActionToRedux(item) + onExpand: _propTypes2.default.func, + onCollapse: _propTypes2.default.func, + // Optional event handler called with the current focused node when the + // Enter key is pressed. Can be useful to allow further keyboard actions + // within the tree node. + onActivate: _propTypes2.default.func, + isExpandable: _propTypes2.default.func, + // Additional classes to add to the root element. + className: _propTypes2.default.string, + // style object to be applied to the root element. + style: _propTypes2.default.object, + // Prevents blur when Tree loses focus + preventBlur: _propTypes2.default.bool + }; + } + + static get defaultProps() { + return { + autoExpandDepth: AUTO_EXPAND_DEPTH, + autoExpandAll: true + }; + } + + constructor(props) { + super(props); + + this.state = { + seen: new Set() + }; + + this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this); + this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this); + this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this); + this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this); + this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this); + this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this); + this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this); + + this._autoExpand = this._autoExpand.bind(this); + this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this); + this._dfs = this._dfs.bind(this); + this._dfsFromRoots = this._dfsFromRoots.bind(this); + this._focus = this._focus.bind(this); + this._scrollNodeIntoView = this._scrollNodeIntoView.bind(this); + this._onBlur = this._onBlur.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + this._nodeIsExpandable = this._nodeIsExpandable.bind(this); + this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this); + } + + componentDidMount() { + this._autoExpand(); + if (this.props.focused) { + this._scrollNodeIntoView(this.props.focused); + // Always keep the focus on the tree itself. + this.treeRef.focus(); + } + } + + componentWillReceiveProps(nextProps) { + this._autoExpand(); + } + + componentDidUpdate(prevProps, prevState) { + if (this.props.focused && prevProps.focused !== this.props.focused) { + this._scrollNodeIntoView(this.props.focused); + // Always keep the focus on the tree itself. + this.treeRef.focus(); + } + } + + _autoExpand() { + if (!this.props.autoExpandDepth) { + return; + } + + // Automatically expand the first autoExpandDepth levels for new items. Do + // not use the usual DFS infrastructure because we don't want to ignore + // collapsed nodes. + const autoExpand = (item, currentDepth) => { + if (currentDepth >= this.props.autoExpandDepth || this.state.seen.has(item)) { + return; + } + + this.props.onExpand(item); + this.state.seen.add(item); + + const children = this.props.getChildren(item); + const length = children.length; + for (let i = 0; i < length; i++) { + autoExpand(children[i], currentDepth + 1); + } + }; + + const roots = this.props.getRoots(); + const length = roots.length; + if (this.props.autoExpandAll) { + for (let i = 0; i < length; i++) { + autoExpand(roots[i], 0); + } + } else if (length != 0) { + autoExpand(roots[0], 0); + } + } + + _preventArrowKeyScrolling(e) { + switch (e.key) { + case "ArrowUp": + case "ArrowDown": + case "ArrowLeft": + case "ArrowRight": + e.preventDefault(); + e.stopPropagation(); + if (e.nativeEvent) { + if (e.nativeEvent.preventDefault) { + e.nativeEvent.preventDefault(); + } + if (e.nativeEvent.stopPropagation) { + e.nativeEvent.stopPropagation(); + } + } + } + } + + /** + * Perform a pre-order depth-first search from item. + */ + _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) { + traversal.push({ item, depth: _depth }); + + if (!this.props.isExpanded(item)) { + return traversal; + } + + const nextDepth = _depth + 1; + + if (nextDepth > maxDepth) { + return traversal; + } + + const children = this.props.getChildren(item); + const length = children.length; + for (let i = 0; i < length; i++) { + this._dfs(children[i], maxDepth, traversal, nextDepth); + } + + return traversal; + } + + /** + * Perform a pre-order depth-first search over the whole forest. + */ + _dfsFromRoots(maxDepth = Infinity) { + const traversal = []; + + const roots = this.props.getRoots(); + const length = roots.length; + for (let i = 0; i < length; i++) { + this._dfs(roots[i], maxDepth, traversal); + } + + return traversal; + } + + /** + * Expands current row. + * + * @param {Object} item + * @param {Boolean} expandAllChildren + */ + _onExpand(item, expandAllChildren) { + if (this.props.onExpand) { + this.props.onExpand(item); + + if (expandAllChildren) { + const children = this._dfs(item); + const length = children.length; + for (let i = 0; i < length; i++) { + this.props.onExpand(children[i].item); + } + } + } + } + + /** + * Collapses current row. + * + * @param {Object} item + */ + _onCollapse(item) { + if (this.props.onCollapse) { + this.props.onCollapse(item); + } + } + + /** + * Sets the passed in item to be the focused item. + * + * @param {Object|undefined} item + * The item to be focused, or undefined to focus no item. + * + * @param {Object|undefined} options + * An options object which can contain: + * - dir: "up" or "down" to indicate if we should scroll the element + * to the top or the bottom of the scrollable container when + * the element is off canvas. + */ + _focus(item, options = {}) { + const { preventAutoScroll } = options; + if (item && !preventAutoScroll) { + this._scrollNodeIntoView(item, options); + } + if (this.props.onFocus) { + this.props.onFocus(item); + } + } + + /** + * Sets the passed in item to be the focused item. + * + * @param {Object|undefined} item + * The item to be scrolled to. + * + * @param {Object|undefined} options + * An options object which can contain: + * - dir: "up" or "down" to indicate if we should scroll the element + * to the top or the bottom of the scrollable container when + * the element is off canvas. + */ + _scrollNodeIntoView(item, options = {}) { + if (item !== undefined) { + const treeElement = this.treeRef; + const element = document.getElementById(this.props.getKey(item)); + + if (element) { + const { top, bottom } = element.getBoundingClientRect(); + const closestScrolledParent = node => { + if (node == null) { + return null; + } + + if (node.scrollHeight > node.clientHeight) { + return node; + } + return closestScrolledParent(node.parentNode); + }; + const scrolledParent = closestScrolledParent(treeElement); + const scrolledParentRect = scrolledParent ? scrolledParent.getBoundingClientRect() : null; + const isVisible = !scrolledParent || top >= scrolledParentRect.top && bottom <= scrolledParentRect.bottom; + + if (!isVisible) { + const { alignTo } = options; + const scrollToTop = alignTo ? alignTo === "top" : !scrolledParentRect || top < scrolledParentRect.top; + element.scrollIntoView(scrollToTop); + } + } + } + } + + /** + * Sets the state to have no focused item. + */ + _onBlur() { + if (!this.props.preventBlur) { + this._focus(undefined); + } + } + + /** + * Handles key down events in the tree's container. + * + * @param {Event} e + */ + _onKeyDown(e) { + if (this.props.focused == null) { + return; + } + + // Allow parent nodes to use navigation arrows with modifiers. + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + return; + } + + this._preventArrowKeyScrolling(e); + + switch (e.key) { + case "ArrowUp": + this._focusPrevNode(); + return; + + case "ArrowDown": + this._focusNextNode(); + return; + + case "ArrowLeft": + if (this.props.isExpanded(this.props.focused) && this._nodeIsExpandable(this.props.focused)) { + this._onCollapse(this.props.focused); + } else { + this._focusParentNode(); + } + return; + + case "ArrowRight": + if (this._nodeIsExpandable(this.props.focused) && !this.props.isExpanded(this.props.focused)) { + this._onExpand(this.props.focused); + } else { + this._focusNextNode(); + } + return; + + case "Home": + this._focusFirstNode(); + return; + + case "End": + this._focusLastNode(); + return; + + case "Enter": + this._activateNode(); + } + } + + /** + * Sets the previous node relative to the currently focused item, to focused. + */ + _focusPrevNode() { + // Start a depth first search and keep going until we reach the currently + // focused node. Focus the previous node in the DFS, if it exists. If it + // doesn't exist, we're at the first node already. + + let prev; + + const traversal = this._dfsFromRoots(); + const length = traversal.length; + for (let i = 0; i < length; i++) { + const item = traversal[i].item; + if (item === this.props.focused) { + break; + } + prev = item; + } + if (prev === undefined) { + return; + } + + this._focus(prev, { alignTo: "top" }); + } + + /** + * Handles the down arrow key which will focus either the next child + * or sibling row. + */ + _focusNextNode() { + // Start a depth first search and keep going until we reach the currently + // focused node. Focus the next node in the DFS, if it exists. If it + // doesn't exist, we're at the last node already. + const traversal = this._dfsFromRoots(); + const length = traversal.length; + let i = 0; + + while (i < length) { + if (traversal[i].item === this.props.focused) { + break; + } + i++; + } + + if (i + 1 < traversal.length) { + this._focus(traversal[i + 1].item, { alignTo: "bottom" }); + } + } + + /** + * Handles the left arrow key, going back up to the current rows' + * parent row. + */ + _focusParentNode() { + const parent = this.props.getParent(this.props.focused); + if (!parent) { + this._focusPrevNode(this.props.focused); + return; + } + + this._focus(parent, { alignTo: "top" }); + } + + _focusFirstNode() { + const traversal = this._dfsFromRoots(); + this._focus(traversal[0].item, { alignTo: "top" }); + } + + _focusLastNode() { + const traversal = this._dfsFromRoots(); + const lastIndex = traversal.length - 1; + this._focus(traversal[lastIndex].item, { alignTo: "bottom" }); + } + + _activateNode() { + if (this.props.onActivate) { + this.props.onActivate(this.props.focused); + } + } + + _nodeIsExpandable(item) { + return this.props.isExpandable ? this.props.isExpandable(item) : !!this.props.getChildren(item).length; + } + + render() { + const traversal = this._dfsFromRoots(); + const { focused } = this.props; + + const nodes = traversal.map((v, i) => { + const { item, depth } = traversal[i]; + const key = this.props.getKey(item, i); + return TreeNodeFactory({ + key, + id: key, + index: i, + item, + depth, + renderItem: this.props.renderItem, + focused: focused === item, + expanded: this.props.isExpanded(item), + isExpandable: this._nodeIsExpandable(item), + onExpand: this._onExpand, + onCollapse: this._onCollapse, + onClick: e => { + // We can stop the propagation since click handler on the node can be + // created in `renderItem`. + e.stopPropagation(); + + // Since the user just clicked the node, there's no need to check if + // it should be scrolled into view. + this._focus(item, { preventAutoScroll: true }); + if (this.props.isExpanded(item)) { + this.props.onCollapse(item); + } else { + this.props.onExpand(item, e.altKey); + } + } + }); + }); + + const style = Object.assign({}, this.props.style || {}, { + padding: 0, + margin: 0 + }); + + return _reactDomFactories2.default.div({ + className: `tree ${this.props.className ? this.props.className : ""}`, + ref: el => { + this.treeRef = el; + }, + role: "tree", + tabIndex: "0", + onKeyDown: this._onKeyDown, + onKeyPress: this._preventArrowKeyScrolling, + onKeyUp: this._preventArrowKeyScrolling, + onFocus: ({ nativeEvent }) => { + if (focused || !nativeEvent || !this.treeRef) { + return; + } + + const { explicitOriginalTarget } = nativeEvent; + // Only set default focus to the first tree node if the focus came + // from outside the tree (e.g. by tabbing to the tree from other + // external elements). + if (explicitOriginalTarget !== this.treeRef && !this.treeRef.contains(explicitOriginalTarget)) { + this._focus(traversal[0].item); + } + }, + onBlur: this._onBlur, + "aria-label": this.props.label, + "aria-labelledby": this.props.labelledby, + "aria-activedescendant": focused && this.props.getKey(focused), + style + }, nodes); + } +} + +exports.default = Tree; + +/***/ }), + +/***/ 3671: +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + +/***/ }), + +/***/ 3672: +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + +/***/ }), + +/***/ 3673: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const { getGripType, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders undefined value + */ +const Undefined = function () { + return span({ className: "objectBox objectBox-undefined" }, "undefined"); +}; + +function supportsObject(object, noGrip = false) { + if (noGrip === true) { + return object === undefined; + } + + return object && object.type && object.type == "undefined" || getGripType(object, noGrip) == "undefined"; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(Undefined), + supportsObject +}; + +/***/ }), + +/***/ 3674: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const { wrapRender } = __webpack_require__(3644); +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders null value + */ +function Null(props) { + return span({ className: "objectBox objectBox-null" }, "null"); +} + +function supportsObject(object, noGrip = false) { + if (noGrip === true) { + return object === null; + } + + if (object && object.type && object.type == "null") { + return true; + } + + return object == null; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(Null), + supportsObject +}; + +/***/ }), + +/***/ 3675: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); + +const { getGripType, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a number + */ +Number.propTypes = { + object: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.bool]).isRequired +}; + +function Number(props) { + const value = props.object; + + return span({ className: "objectBox objectBox-number" }, stringify(value)); +} + +function stringify(object) { + const isNegativeZero = Object.is(object, -0) || object.type && object.type == "-0"; + + return isNegativeZero ? "-0" : String(object); +} + +function supportsObject(object, noGrip = false) { + return ["boolean", "number", "-0"].includes(getGripType(object, noGrip)); +} + +// Exports from this module + +module.exports = { + rep: wrapRender(Number), + supportsObject +}; + +/***/ }), + +/***/ 3676: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); +const { wrapRender, ellipsisElement } = __webpack_require__(3644); +const PropRep = __webpack_require__(3650); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +const DEFAULT_TITLE = "Object"; + +/** + * Renders an object. An object is represented by a list of its + * properties enclosed in curly brackets. + */ +ObjectRep.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + title: PropTypes.string +}; + +function ObjectRep(props) { + const object = props.object; + const propsArray = safePropIterator(props, object); + + if (props.mode === MODE.TINY) { + const tinyModeItems = []; + if (getTitle(props, object) !== DEFAULT_TITLE) { + tinyModeItems.push(getTitleElement(props, object)); + } else { + tinyModeItems.push(span({ + className: "objectLeftBrace" + }, "{"), propsArray.length > 0 ? ellipsisElement : null, span({ + className: "objectRightBrace" + }, "}")); + } + + return span({ className: "objectBox objectBox-object" }, ...tinyModeItems); + } + + return span({ className: "objectBox objectBox-object" }, getTitleElement(props, object), span({ + className: "objectLeftBrace" + }, " { "), ...propsArray, span({ + className: "objectRightBrace" + }, " }")); +} + +function getTitleElement(props, object) { + return span({ className: "objectTitle" }, getTitle(props, object)); +} + +function getTitle(props, object) { + return props.title || DEFAULT_TITLE; +} + +function safePropIterator(props, object, max) { + max = typeof max === "undefined" ? 3 : max; + try { + return propIterator(props, object, max); + } catch (err) { + console.error(err); + } + return []; +} + +function propIterator(props, object, max) { + // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377 + if (Object.prototype.toString.call(object) === "[object Generator]") { + object = Object.getPrototypeOf(object); + } + + const elements = []; + const unimportantProperties = []; + let propertiesNumber = 0; + const propertiesNames = Object.keys(object); + + const pushPropRep = (name, value) => { + elements.push(PropRep(_extends({}, props, { + key: name, + mode: MODE.TINY, + name, + object: value, + equal: ": " + }))); + propertiesNumber++; + + if (propertiesNumber < propertiesNames.length) { + elements.push(", "); + } + }; + + try { + for (const name of propertiesNames) { + if (propertiesNumber >= max) { + break; + } + + let value; + try { + value = object[name]; + } catch (exc) { + continue; + } + + // Object members with non-empty values are preferred since it gives the + // user a better overview of the object. + if (isInterestingProp(value)) { + pushPropRep(name, value); + } else { + // If the property is not important, put its name on an array for later + // use. + unimportantProperties.push(name); + } + } + } catch (err) { + console.error(err); + } + + if (propertiesNumber < max) { + for (const name of unimportantProperties) { + if (propertiesNumber >= max) { + break; + } + + let value; + try { + value = object[name]; + } catch (exc) { + continue; + } + + pushPropRep(name, value); + } + } + + if (propertiesNumber < propertiesNames.length) { + elements.push(ellipsisElement); + } + + return elements; +} + +function isInterestingProp(value) { + const type = typeof value; + return type == "boolean" || type == "number" || type == "string" && value; +} + +function supportsObject(object, noGrip = false) { + return noGrip; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ObjectRep), + supportsObject +}; + +/***/ }), + +/***/ 3677: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); + +const { getGripType, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a symbol. + */ +SymbolRep.propTypes = { + object: PropTypes.object.isRequired +}; + +function SymbolRep(props) { + const { className = "objectBox objectBox-symbol", object } = props; + const { name } = object; + + return span({ + className, + "data-link-actor-id": object.actor + }, `Symbol(${name || ""})`); +} + +function supportsObject(object, noGrip = false) { + return getGripType(object, noGrip) == "symbol"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(SymbolRep), + supportsObject +}; + +/***/ }), + +/***/ 3678: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); + +const { getGripType, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a Infinity object + */ +InfinityRep.propTypes = { + object: PropTypes.object.isRequired +}; + +function InfinityRep(props) { + const { object } = props; + + return span({ className: "objectBox objectBox-number" }, object.type); +} + +function supportsObject(object, noGrip = false) { + const type = getGripType(object, noGrip); + return type == "Infinity" || type == "-Infinity"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(InfinityRep), + supportsObject +}; + +/***/ }), + +/***/ 3679: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const { getGripType, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a NaN object + */ +function NaNRep(props) { + return span({ className: "objectBox objectBox-nan" }, "NaN"); +} + +function supportsObject(object, noGrip = false) { + return getGripType(object, noGrip) == "NaN"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(NaNRep), + supportsObject +}; + +/***/ }), + +/***/ 3680: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const dom = __webpack_require__(3643); +const PropTypes = __webpack_require__(3642); +const { wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); +const { span } = dom; + +/** + * Renders an object. An object is represented by a list of its + * properties enclosed in curly brackets. + */ +Accessor.propTypes = { + object: PropTypes.object.isRequired, + mode: PropTypes.oneOf(Object.values(MODE)) +}; + +function Accessor(props) { + const { object } = props; + + const accessors = []; + if (hasGetter(object)) { + accessors.push("Getter"); + } + if (hasSetter(object)) { + accessors.push("Setter"); + } + const title = accessors.join(" & "); + + return span({ className: "objectBox objectBox-accessor objectTitle" }, title); +} + +function hasGetter(object) { + return object && object.get && object.get.type !== "undefined"; +} + +function hasSetter(object) { + return object && object.set && object.set.type !== "undefined"; +} + +function supportsObject(object, noGrip = false) { + if (noGrip !== true && (hasGetter(object) || hasSetter(object))) { + return true; + } + + return false; +} + +// Exports from this module +module.exports = { + rep: wrapRender(Accessor), + supportsObject +}; + +/***/ }), + +/***/ 3681: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); +const dom = __webpack_require__(3643); +const { span } = dom; + +// Reps +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const { rep: StringRep } = __webpack_require__(3648); + +/** + * Renders DOM attribute + */ +Attribute.propTypes = { + object: PropTypes.object.isRequired +}; + +function Attribute(props) { + const { object } = props; + const value = object.preview.value; + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox-Attr" + }, span({ className: "attrName" }, getTitle(object)), span({ className: "attrEqual" }, "="), StringRep({ className: "attrValue", object: value })); +} + +function getTitle(grip) { + return grip.preview.nodeName; +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return getGripType(grip, noGrip) == "Attr" && grip.preview; +} + +module.exports = { + rep: wrapRender(Attribute), + supportsObject +}; + +/***/ }), + +/***/ 3682: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Used to render JS built-in Date() object. + */ +DateTime.propTypes = { + object: PropTypes.object.isRequired +}; + +function DateTime(props) { + const grip = props.object; + let date; + try { + date = span({ + "data-link-actor-id": grip.actor, + className: "objectBox" + }, getTitle(grip), span({ className: "Date" }, new Date(grip.preview.timestamp).toISOString())); + } catch (e) { + date = span({ className: "objectBox" }, "Invalid Date"); + } + + return date; +} + +function getTitle(grip) { + return span({ + className: "objectTitle" + }, `${grip.class} `); +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return getGripType(grip, noGrip) == "Date" && grip.preview; +} + +// Exports from this module +module.exports = { + rep: wrapRender(DateTime), + supportsObject +}; + +/***/ }), + +/***/ 3683: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { + getGripType, + isGrip, + getURLDisplayString, + wrapRender +} = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders DOM document object. + */ +Document.propTypes = { + object: PropTypes.object.isRequired +}; + +function Document(props) { + const grip = props.object; + const location = getLocation(grip); + return span({ + "data-link-actor-id": grip.actor, + className: "objectBox objectBox-document" + }, getTitle(grip), location ? span({ className: "location" }, ` ${location}`) : null); +} + +function getLocation(grip) { + const location = grip.preview.location; + return location ? getURLDisplayString(location) : null; +} + +function getTitle(grip) { + return span({ + className: "objectTitle" + }, grip.class); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + const type = getGripType(object, noGrip); + return object.preview && (type === "HTMLDocument" || type === "XULDocument"); +} + +// Exports from this module +module.exports = { + rep: wrapRender(Document), + supportsObject +}; + +/***/ }), + +/***/ 3684: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders DOM documentType object. + */ +DocumentType.propTypes = { + object: PropTypes.object.isRequired +}; + +function DocumentType(props) { + const { object } = props; + const name = object && object.preview && object.preview.nodeName ? ` ${object.preview.nodeName}` : ""; + return span({ + "data-link-actor-id": props.object.actor, + className: "objectBox objectBox-document" + }, ``); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + const type = getGripType(object, noGrip); + return object.preview && type === "DocumentType"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(DocumentType), + supportsObject +}; + +/***/ }), + +/***/ 3685: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { isGrip, wrapRender } = __webpack_require__(3644); + +const { MODE } = __webpack_require__(3645); +const { rep } = __webpack_require__(3656); + +/** + * Renders DOM event objects. + */ +Event.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function Event(props) { + const gripProps = _extends({}, props, { + title: getTitle(props), + object: _extends({}, props.object, { + preview: _extends({}, props.object.preview, { + ownProperties: {} + }) + }) + }); + + if (gripProps.object.preview.target) { + Object.assign(gripProps.object.preview.ownProperties, { + target: gripProps.object.preview.target + }); + } + Object.assign(gripProps.object.preview.ownProperties, gripProps.object.preview.properties); + + delete gripProps.object.preview.properties; + gripProps.object.ownPropertyLength = Object.keys(gripProps.object.preview.ownProperties).length; + + switch (gripProps.object.class) { + case "MouseEvent": + gripProps.isInterestingProp = (type, value, name) => { + return ["target", "clientX", "clientY", "layerX", "layerY"].includes(name); + }; + break; + case "KeyboardEvent": + gripProps.isInterestingProp = (type, value, name) => { + return ["target", "key", "charCode", "keyCode"].includes(name); + }; + break; + case "MessageEvent": + gripProps.isInterestingProp = (type, value, name) => { + return ["target", "isTrusted", "data"].includes(name); + }; + break; + default: + gripProps.isInterestingProp = (type, value, name) => { + // We want to show the properties in the order they are declared. + return Object.keys(gripProps.object.preview.ownProperties).includes(name); + }; + } + + return rep(gripProps); +} + +function getTitle(props) { + const preview = props.object.preview; + let title = preview.type; + + if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) { + title = `${title} ${preview.modifiers.join("-")}`; + } + return title; +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && grip.preview.kind == "DOMEvent"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(Event), + supportsObject +}; + +/***/ }), + +/***/ 3686: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); +// Dependencies +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); + +const PropRep = __webpack_require__(3650); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a DOM Promise object. + */ +PromiseRep.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function PromiseRep(props) { + const object = props.object; + const { promiseState } = object; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-object" + }; + + if (props.mode === MODE.TINY) { + const { Rep } = __webpack_require__(3647); + + return span(config, getTitle(object), span({ + className: "objectLeftBrace" + }, " { "), Rep({ object: promiseState.state }), span({ + className: "objectRightBrace" + }, " }")); + } + + const propsArray = getProps(props, promiseState); + return span(config, getTitle(object), span({ + className: "objectLeftBrace" + }, " { "), ...propsArray, span({ + className: "objectRightBrace" + }, " }")); +} + +function getTitle(object) { + return span({ + className: "objectTitle" + }, object.class); +} + +function getProps(props, promiseState) { + const keys = ["state"]; + if (Object.keys(promiseState).includes("value")) { + keys.push("value"); + } + + return keys.reduce((res, key, i) => { + const object = promiseState[key]; + res = res.concat(PropRep(_extends({}, props, { + mode: MODE.TINY, + name: `<${key}>`, + object, + equal: ": ", + suppressQuotes: true + }))); + + // Interleave commas between elements + if (i !== keys.length - 1) { + res.push(", "); + } + + return res; + }, []); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + return getGripType(object, noGrip) == "Promise"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(PromiseRep), + supportsObject +}; + +/***/ }), + +/***/ 3687: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a grip object with regular expression. + */ +RegExp.propTypes = { + object: PropTypes.object.isRequired +}; + +function RegExp(props) { + const { object } = props; + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox objectBox-regexp regexpSource" + }, getSource(object)); +} + +function getSource(grip) { + return grip.displayString; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + return getGripType(object, noGrip) == "RegExp"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(RegExp), + supportsObject +}; + +/***/ }), + +/***/ 3688: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { + getGripType, + isGrip, + getURLDisplayString, + wrapRender +} = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a grip representing CSSStyleSheet + */ +StyleSheet.propTypes = { + object: PropTypes.object.isRequired +}; + +function StyleSheet(props) { + const grip = props.object; + + return span({ + "data-link-actor-id": grip.actor, + className: "objectBox objectBox-object" + }, getTitle(grip), span({ className: "objectPropValue" }, getLocation(grip))); +} + +function getTitle(grip) { + const title = "StyleSheet "; + return span({ className: "objectBoxTitle" }, title); +} + +function getLocation(grip) { + // Embedded stylesheets don't have URL and so, no preview. + const url = grip.preview ? grip.preview.url : ""; + return url ? getURLDisplayString(url) : ""; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + return getGripType(object, noGrip) == "CSSStyleSheet"; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(StyleSheet), + supportsObject +}; + +/***/ }), + +/***/ 3689: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(3642); +const { + isGrip, + cropString, + cropMultipleLines, + wrapRender +} = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); +const nodeConstants = __webpack_require__(3659); +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders DOM comment node. + */ +CommentNode.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])) +}; + +function CommentNode(props) { + const { object, mode = MODE.SHORT } = props; + + let { textContent } = object.preview; + if (mode === MODE.TINY) { + textContent = cropMultipleLines(textContent, 30); + } else if (mode === MODE.SHORT) { + textContent = cropString(textContent, 50); + } + + return span({ + className: "objectBox theme-comment", + "data-link-actor-id": object.actor + }, ``); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + return object.preview && object.preview.nodeType === nodeConstants.COMMENT_NODE; +} + +// Exports from this module +module.exports = { + rep: wrapRender(CommentNode), + supportsObject +}; + +/***/ }), + +/***/ 3690: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Utils +const { isGrip, wrapRender } = __webpack_require__(3644); +const { rep: StringRep } = __webpack_require__(3648); +const { MODE } = __webpack_require__(3645); +const nodeConstants = __webpack_require__(3659); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders DOM element node. + */ +ElementNode.propTypes = { + object: PropTypes.object.isRequired, + inspectIconTitle: PropTypes.string, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeClick: PropTypes.func, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function ElementNode(props) { + const { + object, + inspectIconTitle, + mode, + onDOMNodeClick, + onDOMNodeMouseOver, + onDOMNodeMouseOut, + onInspectIconClick + } = props; + const elements = getElements(object, mode); + + const isInTree = object.preview && object.preview.isConnected === true; + + const baseConfig = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-node" + }; + let inspectIcon; + if (isInTree) { + if (onDOMNodeClick) { + Object.assign(baseConfig, { + onClick: _ => onDOMNodeClick(object), + className: `${baseConfig.className} clickable` + }); + } + + if (onDOMNodeMouseOver) { + Object.assign(baseConfig, { + onMouseOver: _ => onDOMNodeMouseOver(object) + }); + } + + if (onDOMNodeMouseOut) { + Object.assign(baseConfig, { + onMouseOut: onDOMNodeMouseOut + }); + } + + if (onInspectIconClick) { + inspectIcon = dom.button({ + className: "open-inspector", + // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands + title: inspectIconTitle || "Click to select the node in the inspector", + onClick: e => { + if (onDOMNodeClick) { + e.stopPropagation(); + } + + onInspectIconClick(object, e); + } + }); + } + } + + return span(baseConfig, ...elements, inspectIcon); +} + +function getElements(grip, mode) { + const { + attributes, + nodeName, + isAfterPseudoElement, + isBeforePseudoElement + } = grip.preview; + const nodeNameElement = span({ + className: "tag-name" + }, nodeName); + + if (isAfterPseudoElement || isBeforePseudoElement) { + return [span({ className: "attrName" }, `::${isAfterPseudoElement ? "after" : "before"}`)]; + } + + if (mode === MODE.TINY) { + const elements = [nodeNameElement]; + if (attributes.id) { + elements.push(span({ className: "attrName" }, `#${attributes.id}`)); + } + if (attributes.class) { + elements.push(span({ className: "attrName" }, attributes.class.trim().split(/\s+/).map(cls => `.${cls}`).join(""))); + } + return elements; + } + + const attributeKeys = Object.keys(attributes); + if (attributeKeys.includes("class")) { + attributeKeys.splice(attributeKeys.indexOf("class"), 1); + attributeKeys.unshift("class"); + } + if (attributeKeys.includes("id")) { + attributeKeys.splice(attributeKeys.indexOf("id"), 1); + attributeKeys.unshift("id"); + } + const attributeElements = attributeKeys.reduce((arr, name, i, keys) => { + const value = attributes[name]; + const attribute = span({}, span({ className: "attrName" }, name), span({ className: "attrEqual" }, "="), StringRep({ className: "attrValue", object: value })); + + return arr.concat([" ", attribute]); + }, []); + + return [span({ className: "angleBracket" }, "<"), nodeNameElement, ...attributeElements, span({ className: "angleBracket" }, ">")]; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + return object.preview && object.preview.nodeType === nodeConstants.ELEMENT_NODE; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ElementNode), + supportsObject +}; + +/***/ }), + +/***/ 3691: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { isGrip, cropString, wrapRender } = __webpack_require__(3644); +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders DOM #text node. + */ +TextNode.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function TextNode(props) { + const { + object: grip, + mode = MODE.SHORT, + onDOMNodeMouseOver, + onDOMNodeMouseOut, + onInspectIconClick + } = props; + + const baseConfig = { + "data-link-actor-id": grip.actor, + className: "objectBox objectBox-textNode" + }; + let inspectIcon; + const isInTree = grip.preview && grip.preview.isConnected === true; + + if (isInTree) { + if (onDOMNodeMouseOver) { + Object.assign(baseConfig, { + onMouseOver: _ => onDOMNodeMouseOver(grip) + }); + } + + if (onDOMNodeMouseOut) { + Object.assign(baseConfig, { + onMouseOut: onDOMNodeMouseOut + }); + } + + if (onInspectIconClick) { + inspectIcon = dom.button({ + className: "open-inspector", + draggable: false, + // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands + title: "Click to select the node in the inspector", + onClick: e => onInspectIconClick(grip, e) + }); + } + } + + if (mode === MODE.TINY) { + return span(baseConfig, getTitle(grip), inspectIcon); + } + + return span(baseConfig, getTitle(grip), span({ className: "nodeValue" }, " ", `"${getTextContent(grip)}"`), inspectIcon); +} + +function getTextContent(grip) { + return cropString(grip.preview.textContent); +} + +function getTitle(grip) { + const title = "#text"; + return span({}, title); +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && grip.class == "Text"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(TextNode), + supportsObject +}; + +/***/ }), + +/***/ 3692: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { + getGripType, + isGrip, + getURLDisplayString, + wrapRender +} = __webpack_require__(3644); + +const { MODE } = __webpack_require__(3645); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a grip representing a window. + */ +WindowRep.propTypes = { + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + object: PropTypes.object.isRequired +}; + +function WindowRep(props) { + const { mode, object } = props; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-Window" + }; + + if (mode === MODE.TINY) { + return span(config, getTitle(object)); + } + + return span(config, getTitle(object, true), span({ className: "location" }, getLocation(object))); +} + +function getTitle(object, trailingSpace) { + let title = object.displayClass || object.class || "Window"; + if (trailingSpace === true) { + title = `${title} `; + } + return span({ className: "objectTitle" }, title); +} + +function getLocation(object) { + return getURLDisplayString(object.preview.url); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + return object.preview && getGripType(object, noGrip) == "Window"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(WindowRep), + supportsObject +}; + +/***/ }), + +/***/ 3693: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { isGrip, wrapRender } = __webpack_require__(3644); + +const String = __webpack_require__(3648).rep; + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a grip object with textual data. + */ +ObjectWithText.propTypes = { + object: PropTypes.object.isRequired +}; + +function ObjectWithText(props) { + const grip = props.object; + return span({ + "data-link-actor-id": grip.actor, + className: `objectTitle objectBox objectBox-${getType(grip)}` + }, `${getType(grip)} `, getDescription(grip)); +} + +function getType(grip) { + return grip.class; +} + +function getDescription(grip) { + return String({ + object: grip.preview.text + }); +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && grip.preview.kind == "ObjectWithText"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ObjectWithText), + supportsObject +}; + +/***/ }), + +/***/ 3694: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); + +// Reps +const { isGrip, getURLDisplayString, wrapRender } = __webpack_require__(3644); + +const dom = __webpack_require__(3643); +const { span } = dom; + +/** + * Renders a grip object with URL data. + */ +ObjectWithURL.propTypes = { + object: PropTypes.object.isRequired +}; + +function ObjectWithURL(props) { + const grip = props.object; + return span({ + "data-link-actor-id": grip.actor, + className: `objectBox objectBox-${getType(grip)}` + }, getTitle(grip), span({ className: "objectPropValue" }, getDescription(grip))); +} + +function getTitle(grip) { + return span({ className: "objectTitle" }, `${getType(grip)} `); +} + +function getType(grip) { + return grip.class; +} + +function getDescription(grip) { + return getURLDisplayString(grip.preview.url); +} + +// Registration +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && grip.preview.kind == "ObjectWithURL"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ObjectWithURL), + supportsObject +}; + +/***/ }), + +/***/ 3695: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { createElement, createFactory, PureComponent } = __webpack_require__(0); +const { Provider } = __webpack_require__(3592); +const ObjectInspector = createFactory(__webpack_require__(3696)); +const createStore = __webpack_require__(3700); +const Utils = __webpack_require__(3657); +const { renderRep, shouldRenderRootsInReps } = Utils; + +class OI extends PureComponent { + constructor(props) { + super(props); + this.store = createStore(props); + } + + getStore() { + return this.store; + } + + render() { + return createElement(Provider, { store: this.store }, ObjectInspector(this.props)); + } +} + +module.exports = props => { + const { roots } = props; + if (shouldRenderRootsInReps(roots)) { + return renderRep(roots[0], props); + } + return new OI(props); +}; + +/***/ }), + +/***/ 3696: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _devtoolsServices = __webpack_require__(22); + +var _devtoolsServices2 = _interopRequireDefault(_devtoolsServices); + +var _devtoolsComponents = __webpack_require__(3669); + +var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { Component, createFactory } = __webpack_require__(0); +const dom = __webpack_require__(3643); +const { connect } = __webpack_require__(3592); +const { bindActionCreators } = __webpack_require__(3593); + +const { appinfo } = _devtoolsServices2.default; +const isMacOS = appinfo.OS === "Darwin"; + +const Tree = createFactory(_devtoolsComponents2.default.Tree); +__webpack_require__(3697); + +const classnames = __webpack_require__(175); +const { MODE } = __webpack_require__(3645); + +const Utils = __webpack_require__(3657); + +const { + getChildren, + getClosestGripNode, + getParent, + getValue, + nodeHasAccessors, + nodeHasProperties, + nodeIsBlock, + nodeIsDefaultProperties, + nodeIsFunction, + nodeIsGetter, + nodeIsMapEntry, + nodeIsMissingArguments, + nodeIsOptimizedOut, + nodeIsPrimitive, + nodeIsPrototype, + nodeIsSetter, + nodeIsUninitializedBinding, + nodeIsUnmappedBinding, + nodeIsUnscopedBinding, + nodeIsWindow, + nodeIsLongString, + nodeHasFullText +} = Utils.node; + +// This implements a component that renders an interactive inspector +// for looking at JavaScript objects. It expects descriptions of +// objects from the protocol, and will dynamically fetch children +// properties as objects are expanded. +// +// If you want to inspect a single object, pass the name and the +// protocol descriptor of it: +// +// ObjectInspector({ +// name: "foo", +// desc: { writable: true, ..., { value: { actor: "1", ... }}}, +// ... +// }) +// +// If you want multiple top-level objects (like scopes), you can pass +// an array of manually constructed nodes as `roots`: +// +// ObjectInspector({ +// roots: [{ name: ... }, ...], +// ... +// }); + +// There are 3 types of nodes: a simple node with a children array, an +// object that has properties that should be children when they are +// fetched, and a primitive value that should be displayed with no +// children. + +class ObjectInspector extends Component { + constructor(props) { + super(); + this.cachedNodes = new Map(); + + const self = this; + + self.getItemChildren = this.getItemChildren.bind(this); + self.renderTreeItem = this.renderTreeItem.bind(this); + self.setExpanded = this.setExpanded.bind(this); + self.focusItem = this.focusItem.bind(this); + self.getRoots = this.getRoots.bind(this); + } + + shouldComponentUpdate(nextProps) { + const { expandedPaths, focusedItem, loadedProperties, roots } = this.props; + + if (roots !== nextProps.roots) { + // Since the roots changed, we assume the properties did as well, + // so we need to cleanup the component internal state. + + // We can clear the cachedNodes to avoid bugs and memory leaks. + this.cachedNodes.clear(); + // The rootsChanged action will be handled in a middleware to release the + // actors of the old roots, as well as cleanup the state properties + // (expandedPaths, loadedProperties, …). + this.props.rootsChanged(nextProps); + // We don't render right away since the state is going to be changed by + // the rootsChanged action. The `state.forceUpdate` flag will be set + // to `true` so we can execute a new render cycle with the cleaned state. + return false; + } + + if (nextProps.forceUpdate === true) { + return true; + } + + // We should update if: + // - there are new loaded properties + // - OR the expanded paths number changed, and all of them have properties + // loaded + // - OR the expanded paths number did not changed, but old and new sets + // differ + // - OR the focused node changed. + return loadedProperties.size !== nextProps.loadedProperties.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || focusedItem !== nextProps.focusedItem; + } + + componentDidUpdate(prevProps) { + if (this.props.forceUpdate) { + // If the component was updated, we can then reset the forceUpdate flag. + this.props.forceUpdated(); + } + } + + componentWillUnmount() { + const { releaseActor } = this.props; + if (typeof releaseActor !== "function") { + return; + } + + const { actors } = this.props; + for (const actor of actors) { + releaseActor(actor); + } + } + + getItemChildren(item) { + const { loadedProperties } = this.props; + const { cachedNodes } = this; + + return getChildren({ + loadedProperties, + cachedNodes, + item + }); + } + + getRoots() { + return this.props.roots; + } + + getNodeKey(item) { + return item.path && typeof item.path.toString === "function" ? item.path.toString() : JSON.stringify(item); + } + + setExpanded(item, expand) { + if (nodeIsPrimitive(item)) { + return; + } + + const { + createObjectClient, + createLongStringClient, + loadedProperties, + nodeExpand, + nodeCollapse, + recordTelemetryEvent, + roots + } = this.props; + + if (expand === true) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + const isRoot = value && roots.some(root => { + const rootValue = getValue(root); + return rootValue && rootValue.actor === value.actor; + }); + const actor = isRoot || !value ? null : value.actor; + nodeExpand(item, actor, loadedProperties, createObjectClient, createLongStringClient); + + if (recordTelemetryEvent) { + recordTelemetryEvent("object_expanded"); + } + } else { + nodeCollapse(item); + } + } + + focusItem(item) { + const { focusable = true, focusedItem, nodeFocus, onFocus } = this.props; + + if (focusable && focusedItem !== item) { + nodeFocus(item); + if (focusedItem !== item && onFocus) { + onFocus(item); + } + } + } + + // eslint-disable-next-line complexity + getTreeItemLabelAndValue(item, depth, expanded) { + const label = item.name; + const isPrimitive = nodeIsPrimitive(item); + + if (nodeIsOptimizedOut(item)) { + return { + label, + value: dom.span({ className: "unavailable" }, "(optimized away)") + }; + } + + if (nodeIsUninitializedBinding(item)) { + return { + label, + value: dom.span({ className: "unavailable" }, "(uninitialized)") + }; + } + + if (nodeIsUnmappedBinding(item)) { + return { + label, + value: dom.span({ className: "unavailable" }, "(unmapped)") + }; + } + + if (nodeIsUnscopedBinding(item)) { + return { + label, + value: dom.span({ className: "unavailable" }, "(unscoped)") + }; + } + + const itemValue = getValue(item); + const unavailable = isPrimitive && itemValue && itemValue.hasOwnProperty && itemValue.hasOwnProperty("unavailable"); + + if (nodeIsMissingArguments(item) || unavailable) { + return { + label, + value: dom.span({ className: "unavailable" }, "(unavailable)") + }; + } + + if (nodeIsFunction(item) && !nodeIsGetter(item) && !nodeIsSetter(item) && (this.props.mode === MODE.TINY || !this.props.mode)) { + return { + label: Utils.renderRep(item, _extends({}, this.props, { + functionName: label + })) + }; + } + + if (nodeHasProperties(item) || nodeHasAccessors(item) || nodeIsMapEntry(item) || nodeIsLongString(item) || isPrimitive) { + const repProps = _extends({}, this.props); + if (depth > 0) { + repProps.mode = this.props.mode === MODE.LONG ? MODE.SHORT : MODE.TINY; + } + if (expanded) { + repProps.mode = MODE.TINY; + } + + if (nodeIsLongString(item)) { + repProps.member = { + open: nodeHasFullText(item) && expanded + }; + } + + return { + label, + value: Utils.renderRep(item, repProps) + }; + } + + return { + label + }; + } + + renderTreeItemLabel(label, item, depth, focused, expanded) { + if (label === null || typeof label === "undefined") { + return null; + } + + const { onLabelClick } = this.props; + + return dom.span({ + className: "object-label", + onClick: onLabelClick ? event => { + event.stopPropagation(); + + // If the user selected text, bail out. + if (Utils.selection.documentHasSelection()) { + return; + } + + onLabelClick(item, { + depth, + focused, + expanded, + setExpanded: this.setExpanded + }); + } : undefined + }, label); + } + + getTreeTopElementProps(item, depth, focused, expanded) { + const { onCmdCtrlClick, onDoubleClick, dimTopLevelWindow } = this.props; + + const parentElementProps = { + className: classnames("node object-node", { + focused, + lessen: !expanded && (nodeIsDefaultProperties(item) || nodeIsPrototype(item) || dimTopLevelWindow === true && nodeIsWindow(item) && depth === 0), + block: nodeIsBlock(item) + }), + onClick: e => { + if (onCmdCtrlClick && (isMacOS && e.metaKey || !isMacOS && e.ctrlKey)) { + onCmdCtrlClick(item, { + depth, + event: e, + focused, + expanded + }); + e.stopPropagation(); + return; + } + + // If this click happened because the user selected some text, bail out. + // Note that if the user selected some text before and then clicks here, + // the previously selected text will be first unselected, unless the + // user clicked on the arrow itself. Indeed because the arrow is an + // image, clicking on it does not remove any existing text selection. + // So we need to also check if the arrow was clicked. + if (Utils.selection.documentHasSelection() && !(e.target && e.target.matches && e.target.matches(".arrow"))) { + e.stopPropagation(); + } + } + }; + + if (onDoubleClick) { + parentElementProps.onDoubleClick = e => { + e.stopPropagation(); + onDoubleClick(item, { + depth, + focused, + expanded + }); + }; + } + + return parentElementProps; + } + + renderTreeItem(item, depth, focused, arrow, expanded) { + const { label, value } = this.getTreeItemLabelAndValue(item, depth, expanded); + const labelElement = this.renderTreeItemLabel(label, item, depth, focused, expanded); + const delimiter = value && labelElement ? dom.span({ className: "object-delimiter" }, ": ") : null; + + return dom.div(this.getTreeTopElementProps(item, depth, focused, expanded), arrow, labelElement, delimiter, value); + } + + render() { + const { + autoExpandAll = true, + autoExpandDepth = 1, + focusable = true, + disableWrap = false, + expandedPaths, + focusedItem, + inline + } = this.props; + + return Tree({ + className: classnames({ + inline, + nowrap: disableWrap, + "object-inspector": true + }), + autoExpandAll, + autoExpandDepth, + + isExpanded: item => expandedPaths && expandedPaths.has(item.path), + isExpandable: item => nodeIsPrimitive(item) === false, + focused: focusedItem, + + getRoots: this.getRoots, + getParent, + getChildren: this.getItemChildren, + getKey: this.getNodeKey, + + onExpand: item => this.setExpanded(item, true), + onCollapse: item => this.setExpanded(item, false), + onFocus: focusable ? this.focusItem : null, + + renderItem: this.renderTreeItem + }); + } +} + +function mapStateToProps(state, props) { + return { + actors: state.actors, + expandedPaths: state.expandedPaths, + // If the root changes, we want to pass a possibly new focusedItem property + focusedItem: state.roots !== props.roots ? props.focusedItem : state.focusedItem, + loadedProperties: state.loadedProperties, + forceUpdate: state.forceUpdate + }; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(__webpack_require__(3699), dispatch); +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(ObjectInspector); + +/***/ }), + +/***/ 3697: +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + +/***/ }), + +/***/ 3698: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +function documentHasSelection() { + const selection = getSelection(); + if (!selection) { + return false; + } + + return selection.type === "Range"; +} + +module.exports = { + documentHasSelection +}; + +/***/ }), + +/***/ 3699: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const { loadItemProperties } = __webpack_require__(3666); /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +/** + * This action is responsible for expanding a given node, which also means that + * it will call the action responsible to fetch properties. + */ +function nodeExpand(node, actor, loadedProperties, createObjectClient, createLongStringClient) { + return async ({ dispatch }) => { + dispatch({ + type: "NODE_EXPAND", + data: { node } + }); + + if (!loadedProperties.has(node.path)) { + dispatch(nodeLoadProperties(node, actor, loadedProperties, createObjectClient, createLongStringClient)); + } + }; +} + +function nodeCollapse(node) { + return { + type: "NODE_COLLAPSE", + data: { node } + }; +} + +function nodeFocus(node) { + return { + type: "NODE_FOCUS", + data: { node } + }; +} +/* + * This action checks if we need to fetch properties, entries, prototype and + * symbols for a given node. If we do, it will call the appropriate ObjectClient + * functions. + */ +function nodeLoadProperties(item, actor, loadedProperties, createObjectClient, createLongStringClient) { + return async ({ dispatch }) => { + try { + const properties = await loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties); + dispatch(nodePropertiesLoaded(item, actor, properties)); + } catch (e) { + console.error(e); + } + }; +} + +function nodePropertiesLoaded(node, actor, properties) { + return { + type: "NODE_PROPERTIES_LOADED", + data: { node, actor, properties } + }; +} + +/* + * This action is dispatched when the `roots` prop, provided by a consumer of + * the ObjectInspector (inspector, console, …), is modified. It will clean the + * internal state properties (expandedPaths, loadedProperties, …) and release + * the actors consumed with the previous roots. + * It takes a props argument which reflects what is passed by the upper-level + * consumer. + */ +function rootsChanged(props) { + return { + type: "ROOTS_CHANGED", + data: props + }; +} + +/* + * This action will reset the `forceUpdate` flag in the state. + */ +function forceUpdated() { + return { + type: "FORCE_UPDATED" + }; +} + +module.exports = { + forceUpdated, + nodeExpand, + nodeCollapse, + nodeFocus, + nodeLoadProperties, + nodePropertiesLoaded, + rootsChanged +}; + +/***/ }), + +/***/ 3700: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { applyMiddleware, createStore, compose } = __webpack_require__(3593); +const { thunk } = __webpack_require__(3701); +const { + waitUntilService +} = __webpack_require__(3702); +const reducer = __webpack_require__(3703); + +function createInitialState(overrides) { + return _extends({ + actors: new Set(), + expandedPaths: new Set(), + focusedItem: null, + loadedProperties: new Map(), + forceUpdated: false + }, overrides); +} + +function enableStateReinitializer(props) { + return next => (innerReducer, initialState, enhancer) => { + function reinitializerEnhancer(state, action) { + if (action.type !== "ROOTS_CHANGED") { + return innerReducer(state, action); + } + + if (props.releaseActor && initialState.actors) { + initialState.actors.forEach(props.releaseActor); + } + + return _extends({}, action.data, { + actors: new Set(), + expandedPaths: new Set(), + loadedProperties: new Map(), + // Indicates to the component that we do want to render on the next + // render cycle. + forceUpdate: true + }); + } + return next(reinitializerEnhancer, initialState, enhancer); + }; +} + +module.exports = props => { + const middlewares = [thunk]; + + if (props.injectWaitService) { + middlewares.push(waitUntilService); + } + + return createStore(reducer, createInitialState(props), compose(applyMiddleware(...middlewares), enableStateReinitializer(props))); +}; + +/***/ }), + +/***/ 3701: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +/** + * A middleware that allows thunks (functions) to be dispatched. + * If it's a thunk, it is called with `dispatch` and `getState`, + * allowing the action to create multiple actions (most likely + * asynchronously). + */ +function thunk({ dispatch, getState }) { + return next => action => { + return typeof action === "function" ? action({ dispatch, getState }) : next(action); + }; +} +exports.thunk = thunk; + +/***/ }), + +/***/ 3702: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const WAIT_UNTIL_TYPE = "@@service/waitUntil"; +/** + * A middleware which acts like a service, because it is stateful + * and "long-running" in the background. It provides the ability + * for actions to install a function to be run once when a specific + * condition is met by an action coming through the system. Think of + * it as a thunk that blocks until the condition is met. Example: + * + * ```js + * const services = { WAIT_UNTIL: require('wait-service').NAME }; + * + * { type: services.WAIT_UNTIL, + * predicate: action => action.type === "ADD_ITEM", + * run: (dispatch, getState, action) => { + * // Do anything here. You only need to accept the arguments + * // if you need them. `action` is the action that satisfied + * // the predicate. + * } + * } + * ``` + */ +function waitUntilService({ dispatch, getState }) { + let pending = []; + + function checkPending(action) { + const readyRequests = []; + const stillPending = []; + + // Find the pending requests whose predicates are satisfied with + // this action. Wait to run the requests until after we update the + // pending queue because the request handler may synchronously + // dispatch again and run this service (that use case is + // completely valid). + for (const request of pending) { + if (request.predicate(action)) { + readyRequests.push(request); + } else { + stillPending.push(request); + } + } + + pending = stillPending; + for (const request of readyRequests) { + request.run(dispatch, getState, action); + } + } + + return next => action => { + if (action.type === WAIT_UNTIL_TYPE) { + pending.push(action); + return null; + } + const result = next(action); + checkPending(action); + return result; + }; +} + +module.exports = { + WAIT_UNTIL_TYPE, + waitUntilService +}; + +/***/ }), + +/***/ 3703: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +function reducer(state = {}, action) { + const { type, data } = action; + + const cloneState = overrides => _extends({}, state, overrides); + + if (type === "NODE_EXPAND") { + return cloneState({ + expandedPaths: new Set(state.expandedPaths).add(data.node.path) + }); + } + + if (type === "NODE_COLLAPSE") { + const expandedPaths = new Set(state.expandedPaths); + expandedPaths.delete(data.node.path); + return cloneState({ expandedPaths }); + } + + if (type === "NODE_PROPERTIES_LOADED") { + return cloneState({ + actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors, + loadedProperties: new Map(state.loadedProperties).set(data.node.path, action.data.properties) + }); + } + + if (type === "NODE_FOCUS") { + if (state.focusedItem === data.node) { + return state; + } + + return cloneState({ + focusedItem: data.node + }); + } + + if (type === "FORCE_UPDATED") { + return cloneState({ + forceUpdate: false + }); + } + + return state; +} /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + + +module.exports = reducer; + +/***/ }), + +/***/ 3730: +/***/ (function(module, exports, __webpack_require__) { + +module.exports = __webpack_require__(3655); + + +/***/ }), + +/***/ 3787: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(3642); +const { button, span } = __webpack_require__(3643); + +// Utils +const { isGrip, wrapRender } = __webpack_require__(3644); +const { rep: StringRep } = __webpack_require__(3648); + +/** + * Renders Accessible object. + */ +Accessible.propTypes = { + object: PropTypes.object.isRequired, + inspectIconTitle: PropTypes.string, + nameMaxLength: PropTypes.number, + onAccessibleClick: PropTypes.func, + onAccessibleMouseOver: PropTypes.func, + onAccessibleMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + separatorText: PropTypes.string +}; + +function Accessible(props) { + const { + object, + inspectIconTitle, + nameMaxLength, + onAccessibleClick, + onAccessibleMouseOver, + onAccessibleMouseOut, + onInspectIconClick, + separatorText + } = props; + const elements = getElements(object, nameMaxLength, separatorText); + const isInTree = object.preview && object.preview.isConnected === true; + const baseConfig = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-accessible" + }; + + let inspectIcon; + if (isInTree) { + if (onAccessibleClick) { + Object.assign(baseConfig, { + onClick: _ => onAccessibleClick(object), + className: `${baseConfig.className} clickable` + }); + } + + if (onAccessibleMouseOver) { + Object.assign(baseConfig, { + onMouseOver: _ => onAccessibleMouseOver(object) + }); + } + + if (onAccessibleMouseOut) { + Object.assign(baseConfig, { + onMouseOut: onAccessibleMouseOut + }); + } + + if (onInspectIconClick) { + inspectIcon = button({ + className: "open-accessibility-inspector", + title: inspectIconTitle, + onClick: e => { + if (onAccessibleClick) { + e.stopPropagation(); + } + + onInspectIconClick(object, e); + } + }); + } + } + + return span(baseConfig, ...elements, inspectIcon); +} + +function getElements(grip, nameMaxLength, separatorText = ": ") { + const { name, role } = grip.preview; + const elements = []; + + elements.push(span({ className: "accessible-role" }, role)); + if (name) { + elements.push(span({ className: "separator" }, separatorText), StringRep({ + className: "accessible-name", + object: name, + cropLimit: nameMaxLength + })); + } + + return elements; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + return object.preview && object.typeName && object.typeName === "accessible"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(Accessible), + supportsObject +}; + +/***/ }) + +/******/ }); +}); \ No newline at end of file diff --git a/devtools/client/shared/components/reps/reps.js b/devtools/client/shared/components/reps/reps.js index b4988e913e11..44772d102a75 100644 --- a/devtools/client/shared/components/reps/reps.js +++ b/devtools/client/shared/components/reps/reps.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")); + module.exports = factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-redux")); else if(typeof define === 'function' && define.amd) - define(["devtools/client/shared/vendor/react", "Services", "devtools/client/shared/vendor/react-redux", "devtools/client/shared/vendor/redux", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories"], factory); + define(["devtools/client/shared/vendor/react", "Services", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories", "devtools/client/shared/vendor/react-redux"], factory); else { - var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")) : factory(root["devtools/client/shared/vendor/react"], root["Services"], root["devtools/client/shared/vendor/react-redux"], root["devtools/client/shared/vendor/redux"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"]); + var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-redux")) : factory(root["devtools/client/shared/vendor/react"], root["Services"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/react-redux"]); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } -})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_3592__, __WEBPACK_EXTERNAL_MODULE_3593__, __WEBPACK_EXTERNAL_MODULE_3642__, __WEBPACK_EXTERNAL_MODULE_3643__) { +})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_1758__, __WEBPACK_EXTERNAL_MODULE_1759__, __WEBPACK_EXTERNAL_MODULE_1763__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -70,7 +70,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.p = "/assets/build"; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 3730); +/******/ return __webpack_require__(__webpack_require__.s = 2082); /******/ }) /************************************************************************/ /******/ ({ @@ -142,42 +142,21 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! /***/ }), -/***/ 22: +/***/ 1758: /***/ (function(module, exports) { -module.exports = __WEBPACK_EXTERNAL_MODULE_22__; +module.exports = __WEBPACK_EXTERNAL_MODULE_1758__; /***/ }), -/***/ 3592: +/***/ 1759: /***/ (function(module, exports) { -module.exports = __WEBPACK_EXTERNAL_MODULE_3592__; +module.exports = __WEBPACK_EXTERNAL_MODULE_1759__; /***/ }), -/***/ 3593: -/***/ (function(module, exports) { - -module.exports = __WEBPACK_EXTERNAL_MODULE_3593__; - -/***/ }), - -/***/ 3642: -/***/ (function(module, exports) { - -module.exports = __WEBPACK_EXTERNAL_MODULE_3642__; - -/***/ }), - -/***/ 3643: -/***/ (function(module, exports) { - -module.exports = __WEBPACK_EXTERNAL_MODULE_3643__; - -/***/ }), - -/***/ 3644: +/***/ 1760: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -191,7 +170,7 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_3643__; const validProtocols = /(http|https|ftp|data|resource|chrome):/i; const tokenSplitRegex = /(\s|\'|\"|\\)+/; const ELLIPSIS = "\u2026"; -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -610,7 +589,7 @@ module.exports = { /***/ }), -/***/ 3645: +/***/ 1762: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -630,7 +609,14 @@ module.exports = { /***/ }), -/***/ 3647: +/***/ 1763: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_1763__; + +/***/ }), + +/***/ 1768: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -640,42 +626,42 @@ module.exports = { * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -__webpack_require__(3672); +__webpack_require__(1824); // Load all existing rep templates -const Undefined = __webpack_require__(3673); -const Null = __webpack_require__(3674); -const StringRep = __webpack_require__(3648); -const Number = __webpack_require__(3675); -const ArrayRep = __webpack_require__(3649); -const Obj = __webpack_require__(3676); -const SymbolRep = __webpack_require__(3677); -const InfinityRep = __webpack_require__(3678); -const NaNRep = __webpack_require__(3679); -const Accessor = __webpack_require__(3680); +const Undefined = __webpack_require__(1825); +const Null = __webpack_require__(1826); +const StringRep = __webpack_require__(1769); +const Number = __webpack_require__(1827); +const ArrayRep = __webpack_require__(1773); +const Obj = __webpack_require__(1828); +const SymbolRep = __webpack_require__(1829); +const InfinityRep = __webpack_require__(1830); +const NaNRep = __webpack_require__(1831); +const Accessor = __webpack_require__(1832); // DOM types (grips) -const Accessible = __webpack_require__(3787); -const Attribute = __webpack_require__(3681); -const DateTime = __webpack_require__(3682); -const Document = __webpack_require__(3683); -const DocumentType = __webpack_require__(3684); -const Event = __webpack_require__(3685); -const Func = __webpack_require__(3658); -const PromiseRep = __webpack_require__(3686); -const RegExp = __webpack_require__(3687); -const StyleSheet = __webpack_require__(3688); -const CommentNode = __webpack_require__(3689); -const ElementNode = __webpack_require__(3690); -const TextNode = __webpack_require__(3691); -const ErrorRep = __webpack_require__(3660); -const Window = __webpack_require__(3692); -const ObjectWithText = __webpack_require__(3693); -const ObjectWithURL = __webpack_require__(3694); -const GripArray = __webpack_require__(3661); -const GripMap = __webpack_require__(3663); -const GripMapEntry = __webpack_require__(3664); -const Grip = __webpack_require__(3656); +const Accessible = __webpack_require__(1833); +const Attribute = __webpack_require__(1834); +const DateTime = __webpack_require__(1835); +const Document = __webpack_require__(1836); +const DocumentType = __webpack_require__(1837); +const Event = __webpack_require__(1838); +const Func = __webpack_require__(1791); +const PromiseRep = __webpack_require__(1839); +const RegExp = __webpack_require__(1840); +const StyleSheet = __webpack_require__(1841); +const CommentNode = __webpack_require__(1842); +const ElementNode = __webpack_require__(1843); +const TextNode = __webpack_require__(1844); +const ErrorRep = __webpack_require__(1793); +const Window = __webpack_require__(1845); +const ObjectWithText = __webpack_require__(1846); +const ObjectWithURL = __webpack_require__(1847); +const GripArray = __webpack_require__(1794); +const GripMap = __webpack_require__(1796); +const GripMapEntry = __webpack_require__(1797); +const Grip = __webpack_require__(1782); // List of all registered template. // XXX there should be a way for extensions to register a new @@ -770,7 +756,7 @@ module.exports = { /***/ }), -/***/ 3648: +/***/ 1769: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -781,7 +767,7 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); const { containsURL, @@ -794,9 +780,9 @@ const { isGrip, tokenSplitRegex, ELLIPSIS -} = __webpack_require__(3644); +} = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { a, span } = dom; /** @@ -1044,7 +1030,7 @@ module.exports = { /***/ }), -/***/ 3649: +/***/ 1773: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1057,10 +1043,10 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // Dependencies -const dom = __webpack_require__(3643); -const PropTypes = __webpack_require__(3642); -const { wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); +const dom = __webpack_require__(1759); +const PropTypes = __webpack_require__(1758); +const { wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); const { span } = dom; const ModePropType = PropTypes.oneOf( @@ -1152,7 +1138,7 @@ ItemRep.propTypes = { }; function ItemRep(props) { - const { Rep } = __webpack_require__(3647); + const { Rep } = __webpack_require__(1768); const { object, delim, mode } = props; return span({}, Rep(_extends({}, props, { @@ -1184,7 +1170,7 @@ module.exports = { /***/ }), -/***/ 3650: +/***/ 1774: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1197,11 +1183,11 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); -const { maybeEscapePropertyName, wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); +const PropTypes = __webpack_require__(1758); +const { maybeEscapePropertyName, wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); -const { span } = __webpack_require__(3643); +const { span } = __webpack_require__(1759); /** * Property for Obj (local JS objects), Grip (remote JS objects) @@ -1232,8 +1218,8 @@ PropRep.propTypes = { * @return {Array} Array of React elements. */ function PropRep(props) { - const Grip = __webpack_require__(3656); - const { Rep } = __webpack_require__(3647); + const Grip = __webpack_require__(1782); + const { Rep } = __webpack_require__(1768); let { name, mode, equal, suppressQuotes } = props; @@ -1264,7 +1250,7 @@ module.exports = wrapRender(PropRep); /***/ }), -/***/ 3655: +/***/ 1778: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1274,17 +1260,16 @@ module.exports = wrapRender(PropRep); * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -const { MODE } = __webpack_require__(3645); -const { REPS, getRep } = __webpack_require__(3647); -const ObjectInspector = __webpack_require__(3695); -const ObjectInspectorUtils = __webpack_require__(3657); +const { MODE } = __webpack_require__(1762); +const { REPS, getRep } = __webpack_require__(1768); +const objectInspector = __webpack_require__(1848); const { parseURLEncodedText, parseURLParams, maybeEscapePropertyName, getGripPreviewItems -} = __webpack_require__(3644); +} = __webpack_require__(1760); module.exports = { REPS, @@ -1294,13 +1279,12 @@ module.exports = { parseURLEncodedText, parseURLParams, getGripPreviewItems, - ObjectInspector, - ObjectInspectorUtils + objectInspector }; /***/ }), -/***/ 3656: +/***/ 1782: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1313,14 +1297,14 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Dependencies -const { interleave, isGrip, wrapRender } = __webpack_require__(3644); -const PropRep = __webpack_require__(3650); -const { MODE } = __webpack_require__(3645); +const { interleave, isGrip, wrapRender } = __webpack_require__(1760); +const PropRep = __webpack_require__(1774); +const { MODE } = __webpack_require__(1762); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -1416,7 +1400,7 @@ function safePropIterator(props, object, max) { function propIterator(props, object, max) { if (object.preview && Object.keys(object.preview).includes("wrappedValue")) { - const { Rep } = __webpack_require__(3647); + const { Rep } = __webpack_require__(1768); return [Rep({ object: object.preview.wrappedValue, @@ -1605,7 +1589,7 @@ module.exports = Grip; /***/ }), -/***/ 3657: +/***/ 1783: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1617,1271 +1601,13 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -const client = __webpack_require__(3665); -const loadProperties = __webpack_require__(3666); -const node = __webpack_require__(3667); -const { nodeIsError, nodeIsPrimitive } = node; -const selection = __webpack_require__(3698); - -const { MODE } = __webpack_require__(3645); -const { - REPS: { Rep, Grip } -} = __webpack_require__(3647); - - -function shouldRenderRootsInReps(roots) { - if (roots.length > 1) { - return false; - } - - const root = roots[0]; - const name = root && root.name; - return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root)); -} - -function renderRep(item, props) { - return Rep(_extends({}, props, { - object: node.getValue(item), - mode: props.mode || MODE.TINY, - defaultRep: Grip - })); -} - -module.exports = { - client, - loadProperties, - node, - renderRep, - selection, - shouldRenderRootsInReps -}; - -/***/ }), - -/***/ 3658: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// ReactJS -const PropTypes = __webpack_require__(3642); - -// Reps -const { getGripType, isGrip, cropString, wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); - -const dom = __webpack_require__(3643); -const { span } = dom; - -const IGNORED_SOURCE_URLS = ["debugger eval code"]; - -/** - * This component represents a template for Function objects. - */ -FunctionRep.propTypes = { - object: PropTypes.object.isRequired, - parameterNames: PropTypes.array, - onViewSourceInDebugger: PropTypes.func -}; - -function FunctionRep(props) { - const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props; - - let jumpToDefinitionButton; - if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) { - jumpToDefinitionButton = dom.button({ - className: "jump-definition", - draggable: false, - title: "Jump to definition", - onClick: e => { - // Stop the event propagation so we don't trigger ObjectInspector - // expand/collapse. - e.stopPropagation(); - if (recordTelemetryEvent) { - recordTelemetryEvent("jump_to_definition"); - } - onViewSourceInDebugger(grip.location); - } - }); - } - - return span({ - "data-link-actor-id": grip.actor, - className: "objectBox objectBox-function", - // Set dir="ltr" to prevent function parentheses from - // appearing in the wrong direction - dir: "ltr" - }, getTitle(grip, props), getFunctionName(grip, props), "(", ...renderParams(props), ")", jumpToDefinitionButton); -} - -function getTitle(grip, props) { - const { mode } = props; - - if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) { - return null; - } - - let title = mode === MODE.TINY ? "" : "function "; - - if (grip.isGenerator) { - title = mode === MODE.TINY ? "* " : "function* "; - } - - if (grip.isAsync) { - title = `${"async" + " "}${title}`; - } - - return span({ - className: "objectTitle" - }, title); -} - -/** - * Returns a ReactElement representing the function name. - * - * @param {Object} grip : Function grip - * @param {Object} props: Function rep props - */ -function getFunctionName(grip, props = {}) { - let { functionName } = props; - let name; - - if (functionName) { - const end = functionName.length - 1; - functionName = functionName.startsWith('"') && functionName.endsWith('"') ? functionName.substring(1, end) : functionName; - } - - if (grip.displayName != undefined && functionName != undefined && grip.displayName != functionName) { - name = `${functionName}:${grip.displayName}`; - } else { - name = cleanFunctionName(grip.userDisplayName || grip.displayName || grip.name || props.functionName || ""); - } - - return cropString(name, 100); -} - -const objectProperty = /([\w\d]+)$/; -const arrayProperty = /\[(.*?)\]$/; -const functionProperty = /([\w\d]+)[\/\.<]*?$/; -const annonymousProperty = /([\w\d]+)\(\^\)$/; - -/** - * Decodes an anonymous naming scheme that - * spider monkey implements based on "Naming Anonymous JavaScript Functions" - * http://johnjbarton.github.io/nonymous/index.html - * - * @param {String} name : Function name to clean up - * @returns String - */ -function cleanFunctionName(name) { - for (const reg of [objectProperty, arrayProperty, functionProperty, annonymousProperty]) { - const match = reg.exec(name); - if (match) { - return match[1]; - } - } - - return name; -} - -function renderParams(props) { - const { parameterNames = [] } = props; - - return parameterNames.filter(param => param).reduce((res, param, index, arr) => { - res.push(span({ className: "param" }, param)); - if (index < arr.length - 1) { - res.push(span({ className: "delimiter" }, ", ")); - } - return res; - }, []); -} - -// Registration -function supportsObject(grip, noGrip = false) { - const type = getGripType(grip, noGrip); - if (noGrip === true || !isGrip(grip)) { - return type == "function"; - } - - return type == "Function"; -} - -// Exports from this module - -module.exports = { - rep: wrapRender(FunctionRep), - supportsObject, - cleanFunctionName, - // exported for testing purpose. - getFunctionName -}; - -/***/ }), - -/***/ 3659: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -module.exports = { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12, - - // DocumentPosition - DOCUMENT_POSITION_DISCONNECTED: 0x01, - DOCUMENT_POSITION_PRECEDING: 0x02, - DOCUMENT_POSITION_FOLLOWING: 0x04, - DOCUMENT_POSITION_CONTAINS: 0x08, - DOCUMENT_POSITION_CONTAINED_BY: 0x10, - DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20 -}; - -/***/ }), - -/***/ 3660: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// ReactJS -const PropTypes = __webpack_require__(3642); -// Utils -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); -const { cleanFunctionName } = __webpack_require__(3658); -const { isLongString } = __webpack_require__(3648); -const { MODE } = __webpack_require__(3645); - -const dom = __webpack_require__(3643); -const { span } = dom; -const IGNORED_SOURCE_URLS = ["debugger eval code"]; - -/** - * Renders Error objects. - */ -ErrorRep.propTypes = { - object: PropTypes.object.isRequired, - // @TODO Change this to Object.values when supported in Node's version of V8 - mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])) -}; - -function ErrorRep(props) { - const object = props.object; - const preview = object.preview; - - let name; - if (preview && preview.name && preview.kind) { - switch (preview.kind) { - case "Error": - name = preview.name; - break; - case "DOMException": - name = preview.kind; - break; - default: - throw new Error("Unknown preview kind for the Error rep."); - } - } else { - name = "Error"; - } - - const content = []; - - if (props.mode === MODE.TINY) { - content.push(name); - } else { - content.push(`${name}: "${preview.message}"`); - } - - if (preview.stack && props.mode !== MODE.TINY) { - content.push("\n", getStacktraceElements(props, preview)); - } - - return span({ - "data-link-actor-id": object.actor, - className: "objectBox-stackTrace" - }, content); -} - -/** - * Returns a React element reprensenting the Error stacktrace, i.e. - * transform error.stack from: - * - * semicolon@debugger eval code:1:109 - * jkl@debugger eval code:1:63 - * asdf@debugger eval code:1:28 - * @debugger eval code:1:227 - * - * Into a column layout: - * - * semicolon (:8:10) - * jkl (:5:10) - * asdf (:2:10) - * (:11:1) - */ -function getStacktraceElements(props, preview) { - const stack = []; - if (!preview.stack) { - return stack; - } - - const isStacktraceALongString = isLongString(preview.stack); - const stackString = isStacktraceALongString ? preview.stack.initial : preview.stack; - - stackString.split("\n").forEach((frame, index, frames) => { - if (!frame) { - // Skip any blank lines - return; - } - - // If the stacktrace is a longString, don't include the last frame in the - // array, since it is certainly incomplete. - // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833 - // is fixed. - if (isStacktraceALongString && index === frames.length - 1) { - return; - } - - let functionName; - let location; - - // Given the input: "functionName@scriptLocation:2:100" - // Result: [ - // "functionName@scriptLocation:2:100", - // "functionName", - // "scriptLocation:2:100" - // ] - const result = frame.match(/^(.*)@(.*)$/); - if (result && result.length === 3) { - functionName = result[1]; - - // If the resource was loaded by base-loader.js, the location looks like: - // resource://devtools/shared/base-loader.js -> resource://path/to/file.js . - // What's needed is only the last part after " -> ". - location = result[2].split(" -> ").pop(); - } - - if (!functionName) { - functionName = ""; - } - - let onLocationClick; - // Given the input: "scriptLocation:2:100" - // Result: - // ["scriptLocation:2:100", "scriptLocation", "2", "100"] - const locationParts = location.match(/^(.*):(\d+):(\d+)$/); - - if (props.onViewSourceInDebugger && location && locationParts && !IGNORED_SOURCE_URLS.includes(locationParts[1])) { - const [, url, line, column] = locationParts; - onLocationClick = e => { - // Don't trigger ObjectInspector expand/collapse. - e.stopPropagation(); - props.onViewSourceInDebugger({ - url, - line: Number(line), - column: Number(column) - }); - }; - } - - stack.push("\t", span({ - key: `fn${index}`, - className: "objectBox-stackTrace-fn" - }, cleanFunctionName(functionName)), " ", span({ - key: `location${index}`, - className: "objectBox-stackTrace-location", - onClick: onLocationClick, - title: onLocationClick ? `View source in debugger → ${location}` : undefined - }, location), "\n"); - }); - - return span({ - key: "stack", - className: "objectBox-stackTrace-grid" - }, stack); -} - -// Registration -function supportsObject(object, noGrip = false) { - if (noGrip === true || !isGrip(object)) { - return false; - } - return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException"; -} - -// Exports from this module -module.exports = { - rep: wrapRender(ErrorRep), - supportsObject -}; - -/***/ }), - -/***/ 3661: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// Dependencies -const PropTypes = __webpack_require__(3642); - -const { lengthBubble } = __webpack_require__(3662); -const { - interleave, - getGripType, - isGrip, - wrapRender, - ellipsisElement -} = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); - -const dom = __webpack_require__(3643); -const { span } = dom; -const { ModePropType } = __webpack_require__(3649); -const DEFAULT_TITLE = "Array"; - -/** - * Renders an array. The array is enclosed by left and right bracket - * and the max number of rendered items depends on the current mode. - */ -GripArray.propTypes = { - object: PropTypes.object.isRequired, - // @TODO Change this to Object.values when supported in Node's version of V8 - mode: ModePropType, - provider: PropTypes.object, - onDOMNodeMouseOver: PropTypes.func, - onDOMNodeMouseOut: PropTypes.func, - onInspectIconClick: PropTypes.func -}; - -function GripArray(props) { - const { object, mode = MODE.SHORT } = props; - - let brackets; - const needSpace = function (space) { - return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" }; - }; - - const config = { - "data-link-actor-id": object.actor, - className: "objectBox objectBox-array" - }; - - const title = getTitle(props, object); - - if (mode === MODE.TINY) { - const isEmpty = getLength(object) === 0; - - // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets). - if (!isEmpty && object.class !== "Array") { - return span(config, title); - } - - brackets = needSpace(false); - return span(config, title, span({ - className: "arrayLeftBracket" - }, brackets.left), isEmpty ? null : ellipsisElement, span({ - className: "arrayRightBracket" - }, brackets.right)); - } - - const max = maxLengthMap.get(mode); - const items = arrayIterator(props, object, max); - brackets = needSpace(items.length > 0); - - return span({ - "data-link-actor-id": object.actor, - className: "objectBox objectBox-array" - }, title, span({ - className: "arrayLeftBracket" - }, brackets.left), ...interleave(items, ", "), span({ - className: "arrayRightBracket" - }, brackets.right), span({ - className: "arrayProperties", - role: "group" - })); -} - -function getLength(grip) { - if (!grip.preview) { - return 0; - } - - return grip.preview.length || grip.preview.childNodesLength || 0; -} - -function getTitle(props, object) { - const objectLength = getLength(object); - const isEmpty = objectLength === 0; - - let title = props.title || object.class || DEFAULT_TITLE; - - const length = lengthBubble({ - object, - mode: props.mode, - maxLengthMap, - getLength - }); - - if (props.mode === MODE.TINY) { - if (isEmpty) { - if (object.class === DEFAULT_TITLE) { - return null; - } - - return span({ className: "objectTitle" }, `${title} `); - } - - let trailingSpace; - if (object.class === DEFAULT_TITLE) { - title = null; - trailingSpace = " "; - } - - return span({ className: "objectTitle" }, title, length, trailingSpace); - } - - return span({ className: "objectTitle" }, title, length, " "); -} - -function getPreviewItems(grip) { - if (!grip.preview) { - return null; - } - - return grip.preview.items || grip.preview.childNodes || []; -} - -function arrayIterator(props, grip, max) { - const { Rep } = __webpack_require__(3647); - - let items = []; - const gripLength = getLength(grip); - - if (!gripLength) { - return items; - } - - const previewItems = getPreviewItems(grip); - const provider = props.provider; - - let emptySlots = 0; - let foldedEmptySlots = 0; - items = previewItems.reduce((res, itemGrip) => { - if (res.length >= max) { - return res; - } - - let object; - try { - if (!provider && itemGrip === null) { - emptySlots++; - return res; - } - - object = provider ? provider.getValue(itemGrip) : itemGrip; - } catch (exc) { - object = exc; - } - - if (emptySlots > 0) { - res.push(getEmptySlotsElement(emptySlots)); - foldedEmptySlots = foldedEmptySlots + emptySlots - 1; - emptySlots = 0; - } - - if (res.length < max) { - res.push(Rep(_extends({}, props, { - object, - mode: MODE.TINY, - // Do not propagate title to array items reps - title: undefined - }))); - } - - return res; - }, []); - - // Handle trailing empty slots if there are some. - if (items.length < max && emptySlots > 0) { - items.push(getEmptySlotsElement(emptySlots)); - foldedEmptySlots = foldedEmptySlots + emptySlots - 1; - } - - const itemsShown = items.length + foldedEmptySlots; - if (gripLength > itemsShown) { - items.push(ellipsisElement); - } - - return items; -} - -function getEmptySlotsElement(number) { - // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141 - return `<${number} empty slot${number > 1 ? "s" : ""}>`; -} - -function supportsObject(grip, noGrip = false) { - if (noGrip === true || !isGrip(grip)) { - return false; - } - - return grip.preview && (grip.preview.kind == "ArrayLike" || getGripType(grip, noGrip) === "DocumentFragment"); -} - -const maxLengthMap = new Map(); -maxLengthMap.set(MODE.SHORT, 3); -maxLengthMap.set(MODE.LONG, 10); - -// Exports from this module -module.exports = { - rep: wrapRender(GripArray), - supportsObject, - maxLengthMap, - getLength -}; - -/***/ }), - -/***/ 3662: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -const PropTypes = __webpack_require__(3642); - -const { wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); -const { ModePropType } = __webpack_require__(3649); - -const dom = __webpack_require__(3643); -const { span } = dom; - -GripLengthBubble.propTypes = { - object: PropTypes.object.isRequired, - maxLengthMap: PropTypes.instanceOf(Map).isRequired, - getLength: PropTypes.func.isRequired, - mode: ModePropType, - visibilityThreshold: PropTypes.number -}; - -function GripLengthBubble(props) { - const { - object, - mode = MODE.SHORT, - visibilityThreshold = 2, - maxLengthMap, - getLength, - showZeroLength = false - } = props; - - const length = getLength(object); - const isEmpty = length === 0; - const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold; - if (isEmpty && !showZeroLength || isObvious) { - return ""; - } - - return span({ - className: "objectLengthBubble" - }, `(${length})`); -} - -module.exports = { - lengthBubble: wrapRender(GripLengthBubble) -}; - -/***/ }), - -/***/ 3663: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// Dependencies - -const { lengthBubble } = __webpack_require__(3662); -const PropTypes = __webpack_require__(3642); -const { - interleave, - isGrip, - wrapRender, - ellipsisElement -} = __webpack_require__(3644); -const PropRep = __webpack_require__(3650); -const { MODE } = __webpack_require__(3645); -const { ModePropType } = __webpack_require__(3649); - -const { span } = __webpack_require__(3643); - -/** - * Renders an map. A map is represented by a list of its - * entries enclosed in curly brackets. - */ -GripMap.propTypes = { - object: PropTypes.object, - // @TODO Change this to Object.values when supported in Node's version of V8 - mode: ModePropType, - isInterestingEntry: PropTypes.func, - onDOMNodeMouseOver: PropTypes.func, - onDOMNodeMouseOut: PropTypes.func, - onInspectIconClick: PropTypes.func, - title: PropTypes.string -}; - -function GripMap(props) { - const { mode, object } = props; - - const config = { - "data-link-actor-id": object.actor, - className: "objectBox objectBox-object" - }; - - const title = getTitle(props, object); - const isEmpty = getLength(object) === 0; - - if (isEmpty || mode === MODE.TINY) { - return span(config, title); - } - - const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode)); - - return span(config, title, span({ - className: "objectLeftBrace" - }, " { "), ...interleave(propsArray, ", "), span({ - className: "objectRightBrace" - }, " }")); -} - -function getTitle(props, object) { - const title = props.title || (object && object.class ? object.class : "Map"); - return span({ - className: "objectTitle" - }, title, lengthBubble({ - object, - mode: props.mode, - maxLengthMap, - getLength, - showZeroLength: true - })); -} - -function safeEntriesIterator(props, object, max) { - max = typeof max === "undefined" ? 3 : max; - try { - return entriesIterator(props, object, max); - } catch (err) { - console.error(err); - } - return []; -} - -function entriesIterator(props, object, max) { - // Entry filter. Show only interesting entries to the user. - const isInterestingEntry = props.isInterestingEntry || ((type, value) => { - return type == "boolean" || type == "number" || type == "string" && value.length != 0; - }); - - const mapEntries = object.preview && object.preview.entries ? object.preview.entries : []; - - let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry); - if (indexes.length < max && indexes.length < mapEntries.length) { - // There are not enough entries yet, so we add uninteresting entries. - indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => { - return !isInterestingEntry(t, value, name); - })); - } - - const entries = getEntries(props, mapEntries, indexes); - if (entries.length < getLength(object)) { - // There are some undisplayed entries. Then display "…". - entries.push(ellipsisElement); - } - - return entries; -} - -/** - * Get entries ordered by index. - * - * @param {Object} props Component props. - * @param {Array} entries Entries array. - * @param {Array} indexes Indexes of entries. - * @return {Array} Array of PropRep. - */ -function getEntries(props, entries, indexes) { - const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props; - - // Make indexes ordered by ascending. - indexes.sort(function (a, b) { - return a - b; - }); - - return indexes.map((index, i) => { - const [key, entryValue] = entries[index]; - const value = entryValue.value !== undefined ? entryValue.value : entryValue; - - return PropRep({ - name: key, - equal: " \u2192 ", - object: value, - mode: MODE.TINY, - onDOMNodeMouseOver, - onDOMNodeMouseOut, - onInspectIconClick - }); - }); -} - -/** - * Get the indexes of entries in the map. - * - * @param {Array} entries Entries array. - * @param {Number} max The maximum length of indexes array. - * @param {Function} filter Filter the entry you want. - * @return {Array} Indexes of filtered entries in the map. - */ -function getEntriesIndexes(entries, max, filter) { - return entries.reduce((indexes, [key, entry], i) => { - if (indexes.length < max) { - const value = entry && entry.value !== undefined ? entry.value : entry; - // Type is specified in grip's "class" field and for primitive - // values use typeof. - const type = (value && value.class ? value.class : typeof value).toLowerCase(); - - if (filter(type, value, key)) { - indexes.push(i); - } - } - - return indexes; - }, []); -} - -function getLength(grip) { - return grip.preview.size || 0; -} - -function supportsObject(grip, noGrip = false) { - if (noGrip === true || !isGrip(grip)) { - return false; - } - return grip.preview && grip.preview.kind == "MapLike"; -} - -const maxLengthMap = new Map(); -maxLengthMap.set(MODE.SHORT, 3); -maxLengthMap.set(MODE.LONG, 10); - -// Exports from this module -module.exports = { - rep: wrapRender(GripMap), - supportsObject, - maxLengthMap, - getLength -}; - -/***/ }), - -/***/ 3664: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// Dependencies -const PropTypes = __webpack_require__(3642); -// Shortcuts -const dom = __webpack_require__(3643); -const { span } = dom; -const { wrapRender } = __webpack_require__(3644); -const PropRep = __webpack_require__(3650); -const { MODE } = __webpack_require__(3645); -/** - * Renders an map entry. A map entry is represented by its key, - * a column and its value. - */ -GripMapEntry.propTypes = { - object: PropTypes.object, - // @TODO Change this to Object.values when supported in Node's version of V8 - mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), - onDOMNodeMouseOver: PropTypes.func, - onDOMNodeMouseOut: PropTypes.func, - onInspectIconClick: PropTypes.func -}; - -function GripMapEntry(props) { - const { object } = props; - - const { key, value } = object.preview; - - return span({ - className: "objectBox objectBox-map-entry" - }, PropRep(_extends({}, props, { - name: key, - object: value, - equal: " \u2192 ", - title: null, - suppressQuotes: false - }))); -} - -function supportsObject(grip, noGrip = false) { - if (noGrip === true) { - return false; - } - return grip && (grip.type === "mapEntry" || grip.type === "storageEntry") && grip.preview; -} - -function createGripMapEntry(key, value) { - return { - type: "mapEntry", - preview: { - key, - value - } - }; -} - -// Exports from this module -module.exports = { - rep: wrapRender(GripMapEntry), - createGripMapEntry, - supportsObject -}; - -/***/ }), - -/***/ 3665: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const { getValue, nodeHasFullText } = __webpack_require__(3667); /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -async function enumIndexedProperties(objectClient, start, end) { - try { - const { iterator } = await objectClient.enumProperties({ - ignoreNonIndexedProperties: true - }); - const response = await iteratorSlice(iterator, start, end); - return response; - } catch (e) { - console.error("Error in enumIndexedProperties", e); - return {}; - } -} - -async function enumNonIndexedProperties(objectClient, start, end) { - try { - const { iterator } = await objectClient.enumProperties({ - ignoreIndexedProperties: true - }); - const response = await iteratorSlice(iterator, start, end); - return response; - } catch (e) { - console.error("Error in enumNonIndexedProperties", e); - return {}; - } -} - -async function enumEntries(objectClient, start, end) { - try { - const { iterator } = await objectClient.enumEntries(); - const response = await iteratorSlice(iterator, start, end); - return response; - } catch (e) { - console.error("Error in enumEntries", e); - return {}; - } -} - -async function enumSymbols(objectClient, start, end) { - try { - const { iterator } = await objectClient.enumSymbols(); - const response = await iteratorSlice(iterator, start, end); - return response; - } catch (e) { - console.error("Error in enumSymbols", e); - return {}; - } -} - -async function getPrototype(objectClient) { - if (typeof objectClient.getPrototype !== "function") { - console.error("objectClient.getPrototype is not a function"); - return Promise.resolve({}); - } - return objectClient.getPrototype(); -} - -async function getFullText(longStringClient, item) { - const { initial, fullText, length } = getValue(item); - - // Return fullText property if it exists so that it can be added to the - // loadedProperties map. - if (nodeHasFullText(item)) { - return Promise.resolve({ fullText }); - } - - return new Promise((resolve, reject) => { - longStringClient.substring(initial.length, length, response => { - if (response.error) { - console.error("LongStringClient.substring", `${response.error}: ${response.message}`); - reject({}); - return; - } - - resolve({ - fullText: initial + response.substring - }); - }); - }); -} - -function iteratorSlice(iterator, start, end) { - start = start || 0; - const count = end ? end - start + 1 : iterator.count; - - if (count === 0) { - return Promise.resolve({}); - } - return iterator.slice(start, count); -} - -module.exports = { - enumEntries, - enumIndexedProperties, - enumNonIndexedProperties, - enumSymbols, - getPrototype, - getFullText -}; - -/***/ }), - -/***/ 3666: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -const { - enumEntries, - enumIndexedProperties, - enumNonIndexedProperties, - getPrototype, - enumSymbols, - getFullText -} = __webpack_require__(3665); - -const { - getClosestGripNode, - getClosestNonBucketNode, - getValue, - nodeHasAccessors, - nodeHasAllEntriesInPreview, - nodeHasProperties, - nodeIsBucket, - nodeIsDefaultProperties, - nodeIsEntries, - nodeIsMapEntry, - nodeIsPrimitive, - nodeIsProxy, - nodeNeedsNumericalBuckets, - nodeIsLongString -} = __webpack_require__(3667); - -function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) { - const gripItem = getClosestGripNode(item); - const value = getValue(gripItem); - - const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : []; - - const promises = []; - let objectClient; - const getObjectClient = () => objectClient || createObjectClient(value); - - if (shouldLoadItemIndexedProperties(item, loadedProperties)) { - promises.push(enumIndexedProperties(getObjectClient(), start, end)); - } - - if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) { - promises.push(enumNonIndexedProperties(getObjectClient(), start, end)); - } - - if (shouldLoadItemEntries(item, loadedProperties)) { - promises.push(enumEntries(getObjectClient(), start, end)); - } - - if (shouldLoadItemPrototype(item, loadedProperties)) { - promises.push(getPrototype(getObjectClient())); - } - - if (shouldLoadItemSymbols(item, loadedProperties)) { - promises.push(enumSymbols(getObjectClient(), start, end)); - } - - if (shouldLoadItemFullText(item, loadedProperties)) { - promises.push(getFullText(createLongStringClient(value), item)); - } - - return Promise.all(promises).then(mergeResponses); -} - -function mergeResponses(responses) { - const data = {}; - - for (const response of responses) { - if (response.hasOwnProperty("ownProperties")) { - data.ownProperties = _extends({}, data.ownProperties, response.ownProperties); - } - - if (response.ownSymbols && response.ownSymbols.length > 0) { - data.ownSymbols = response.ownSymbols; - } - - if (response.prototype) { - data.prototype = response.prototype; - } - - if (response.fullText) { - data.fullText = response.fullText; - } - } - - return data; -} - -function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) { - const gripItem = getClosestGripNode(item); - const value = getValue(gripItem); - - return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeNeedsNumericalBuckets(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && - // The data is loaded when expanding the window node. - !nodeIsDefaultProperties(item); -} - -function shouldLoadItemNonIndexedProperties(item, loadedProperties = new Map()) { - const gripItem = getClosestGripNode(item); - const value = getValue(gripItem); - - return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && !nodeIsBucket(item) && - // The data is loaded when expanding the window node. - !nodeIsDefaultProperties(item); -} - -function shouldLoadItemEntries(item, loadedProperties = new Map()) { - const gripItem = getClosestGripNode(item); - const value = getValue(gripItem); - - return value && nodeIsEntries(getClosestNonBucketNode(item)) && !nodeHasAllEntriesInPreview(gripItem) && !loadedProperties.has(item.path) && !nodeNeedsNumericalBuckets(item); -} - -function shouldLoadItemPrototype(item, loadedProperties = new Map()) { - const value = getValue(item); - - return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item); -} - -function shouldLoadItemSymbols(item, loadedProperties = new Map()) { - const value = getValue(item); - - return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item); -} - -function shouldLoadItemFullText(item, loadedProperties = new Map()) { - return !loadedProperties.has(item.path) && nodeIsLongString(item); -} - -module.exports = { - loadItemProperties, - mergeResponses, - shouldLoadItemEntries, - shouldLoadItemIndexedProperties, - shouldLoadItemNonIndexedProperties, - shouldLoadItemPrototype, - shouldLoadItemSymbols, - shouldLoadItemFullText -}; - -/***/ }), - -/***/ 3667: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -const { maybeEscapePropertyName } = __webpack_require__(3644); -const ArrayRep = __webpack_require__(3649); -const GripArrayRep = __webpack_require__(3661); -const GripMap = __webpack_require__(3663); -const GripMapEntryRep = __webpack_require__(3664); -const ErrorRep = __webpack_require__(3660); -const { isLongString } = __webpack_require__(3648); +const { maybeEscapePropertyName } = __webpack_require__(1760); +const ArrayRep = __webpack_require__(1773); +const GripArrayRep = __webpack_require__(1794); +const GripMap = __webpack_require__(1796); +const GripMapEntryRep = __webpack_require__(1797); +const ErrorRep = __webpack_require__(1793); +const { isLongString } = __webpack_require__(1769); const MAX_NUMERICAL_PROPERTIES = 100; @@ -2929,6 +1655,22 @@ function getValue(item) { return undefined; } +function getActor(item, roots) { + const isRoot = isNodeRoot(item, roots); + const value = getValue(item); + return isRoot || !value ? null : value.actor; +} + +function isNodeRoot(item, roots) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && roots.some(root => { + const rootValue = getValue(root); + return rootValue && rootValue.actor === value.actor; + }); +} + function nodeIsBucket(item) { return getType(item) === NODE_TYPES.BUCKET; } @@ -3596,6 +2338,7 @@ function getClosestNonBucketNode(item) { module.exports = { createNode, + getActor, getChildren, getClosestGripNode, getClosestNonBucketNode, @@ -3642,13 +2385,103 @@ module.exports = { /***/ }), -/***/ 3669: +/***/ 1784: /***/ (function(module, exports, __webpack_require__) { "use strict"; -var _tree = __webpack_require__(3670); +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +function initialState() { + return { + expandedPaths: new Set(), + loadedProperties: new Map(), + actors: new Set() + }; +} /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + + +function reducer(state = initialState(), action = {}) { + const { type, data } = action; + + const cloneState = overrides => _extends({}, state, overrides); + + if (type === "NODE_EXPAND") { + return cloneState({ + expandedPaths: new Set(state.expandedPaths).add(data.node.path) + }); + } + + if (type === "NODE_COLLAPSE") { + const expandedPaths = new Set(state.expandedPaths); + expandedPaths.delete(data.node.path); + return cloneState({ expandedPaths }); + } + + if (type === "NODE_PROPERTIES_LOADED") { + return cloneState({ + actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors, + loadedProperties: new Map(state.loadedProperties).set(data.node.path, action.data.properties) + }); + } + + if (type === "ROOTS_CHANGED") { + return cloneState(); + } + + return state; +} + +function getObjectInspectorState(state) { + return state.objectInspector; +} + +function getExpandedPaths(state) { + return getObjectInspectorState(state).expandedPaths; +} + +function getExpandedPathKeys(state) { + return [...getExpandedPaths(state).keys()]; +} + +function getActors(state) { + return getObjectInspectorState(state).actors; +} + +function getLoadedProperties(state) { + return getObjectInspectorState(state).loadedProperties; +} + +function getLoadedPropertyKeys(state) { + return [...getLoadedProperties(state).keys()]; +} + +const selectors = { + getExpandedPaths, + getExpandedPathKeys, + getActors, + getLoadedProperties, + getLoadedPropertyKeys +}; + +Object.defineProperty(module.exports, "__esModule", { + value: true +}); +module.exports = selectors; +module.exports.default = reducer; + +/***/ }), + +/***/ 1788: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _tree = __webpack_require__(1798); var _tree2 = _interopRequireDefault(_tree); @@ -3662,7 +2495,948 @@ module.exports = { /***/ }), -/***/ 3670: +/***/ 1791: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(1758); + +// Reps +const { getGripType, isGrip, cropString, wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); + +const dom = __webpack_require__(1759); +const { span } = dom; + +const IGNORED_SOURCE_URLS = ["debugger eval code"]; + +/** + * This component represents a template for Function objects. + */ +FunctionRep.propTypes = { + object: PropTypes.object.isRequired, + parameterNames: PropTypes.array, + onViewSourceInDebugger: PropTypes.func +}; + +function FunctionRep(props) { + const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props; + + let jumpToDefinitionButton; + if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) { + jumpToDefinitionButton = dom.button({ + className: "jump-definition", + draggable: false, + title: "Jump to definition", + onClick: e => { + // Stop the event propagation so we don't trigger ObjectInspector + // expand/collapse. + e.stopPropagation(); + if (recordTelemetryEvent) { + recordTelemetryEvent("jump_to_definition"); + } + onViewSourceInDebugger(grip.location); + } + }); + } + + return span({ + "data-link-actor-id": grip.actor, + className: "objectBox objectBox-function", + // Set dir="ltr" to prevent function parentheses from + // appearing in the wrong direction + dir: "ltr" + }, getTitle(grip, props), getFunctionName(grip, props), "(", ...renderParams(props), ")", jumpToDefinitionButton); +} + +function getTitle(grip, props) { + const { mode } = props; + + if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) { + return null; + } + + let title = mode === MODE.TINY ? "" : "function "; + + if (grip.isGenerator) { + title = mode === MODE.TINY ? "* " : "function* "; + } + + if (grip.isAsync) { + title = `${"async" + " "}${title}`; + } + + return span({ + className: "objectTitle" + }, title); +} + +/** + * Returns a ReactElement representing the function name. + * + * @param {Object} grip : Function grip + * @param {Object} props: Function rep props + */ +function getFunctionName(grip, props = {}) { + let { functionName } = props; + let name; + + if (functionName) { + const end = functionName.length - 1; + functionName = functionName.startsWith('"') && functionName.endsWith('"') ? functionName.substring(1, end) : functionName; + } + + if (grip.displayName != undefined && functionName != undefined && grip.displayName != functionName) { + name = `${functionName}:${grip.displayName}`; + } else { + name = cleanFunctionName(grip.userDisplayName || grip.displayName || grip.name || props.functionName || ""); + } + + return cropString(name, 100); +} + +const objectProperty = /([\w\d]+)$/; +const arrayProperty = /\[(.*?)\]$/; +const functionProperty = /([\w\d]+)[\/\.<]*?$/; +const annonymousProperty = /([\w\d]+)\(\^\)$/; + +/** + * Decodes an anonymous naming scheme that + * spider monkey implements based on "Naming Anonymous JavaScript Functions" + * http://johnjbarton.github.io/nonymous/index.html + * + * @param {String} name : Function name to clean up + * @returns String + */ +function cleanFunctionName(name) { + for (const reg of [objectProperty, arrayProperty, functionProperty, annonymousProperty]) { + const match = reg.exec(name); + if (match) { + return match[1]; + } + } + + return name; +} + +function renderParams(props) { + const { parameterNames = [] } = props; + + return parameterNames.filter(param => param).reduce((res, param, index, arr) => { + res.push(span({ className: "param" }, param)); + if (index < arr.length - 1) { + res.push(span({ className: "delimiter" }, ", ")); + } + return res; + }, []); +} + +// Registration +function supportsObject(grip, noGrip = false) { + const type = getGripType(grip, noGrip); + if (noGrip === true || !isGrip(grip)) { + return type == "function"; + } + + return type == "Function"; +} + +// Exports from this module + +module.exports = { + rep: wrapRender(FunctionRep), + supportsObject, + cleanFunctionName, + // exported for testing purpose. + getFunctionName +}; + +/***/ }), + +/***/ 1792: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +module.exports = { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12, + + // DocumentPosition + DOCUMENT_POSITION_DISCONNECTED: 0x01, + DOCUMENT_POSITION_PRECEDING: 0x02, + DOCUMENT_POSITION_FOLLOWING: 0x04, + DOCUMENT_POSITION_CONTAINS: 0x08, + DOCUMENT_POSITION_CONTAINED_BY: 0x10, + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20 +}; + +/***/ }), + +/***/ 1793: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(1758); +// Utils +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); +const { cleanFunctionName } = __webpack_require__(1791); +const { isLongString } = __webpack_require__(1769); +const { MODE } = __webpack_require__(1762); + +const dom = __webpack_require__(1759); +const { span } = dom; +const IGNORED_SOURCE_URLS = ["debugger eval code"]; + +/** + * Renders Error objects. + */ +ErrorRep.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])) +}; + +function ErrorRep(props) { + const object = props.object; + const preview = object.preview; + + let name; + if (preview && preview.name && preview.kind) { + switch (preview.kind) { + case "Error": + name = preview.name; + break; + case "DOMException": + name = preview.kind; + break; + default: + throw new Error("Unknown preview kind for the Error rep."); + } + } else { + name = "Error"; + } + + const content = []; + + if (props.mode === MODE.TINY) { + content.push(name); + } else { + content.push(`${name}: "${preview.message}"`); + } + + if (preview.stack && props.mode !== MODE.TINY) { + content.push("\n", getStacktraceElements(props, preview)); + } + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox-stackTrace" + }, content); +} + +/** + * Returns a React element reprensenting the Error stacktrace, i.e. + * transform error.stack from: + * + * semicolon@debugger eval code:1:109 + * jkl@debugger eval code:1:63 + * asdf@debugger eval code:1:28 + * @debugger eval code:1:227 + * + * Into a column layout: + * + * semicolon (:8:10) + * jkl (:5:10) + * asdf (:2:10) + * (:11:1) + */ +function getStacktraceElements(props, preview) { + const stack = []; + if (!preview.stack) { + return stack; + } + + const isStacktraceALongString = isLongString(preview.stack); + const stackString = isStacktraceALongString ? preview.stack.initial : preview.stack; + + stackString.split("\n").forEach((frame, index, frames) => { + if (!frame) { + // Skip any blank lines + return; + } + + // If the stacktrace is a longString, don't include the last frame in the + // array, since it is certainly incomplete. + // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833 + // is fixed. + if (isStacktraceALongString && index === frames.length - 1) { + return; + } + + let functionName; + let location; + + // Given the input: "functionName@scriptLocation:2:100" + // Result: [ + // "functionName@scriptLocation:2:100", + // "functionName", + // "scriptLocation:2:100" + // ] + const result = frame.match(/^(.*)@(.*)$/); + if (result && result.length === 3) { + functionName = result[1]; + + // If the resource was loaded by base-loader.js, the location looks like: + // resource://devtools/shared/base-loader.js -> resource://path/to/file.js . + // What's needed is only the last part after " -> ". + location = result[2].split(" -> ").pop(); + } + + if (!functionName) { + functionName = ""; + } + + let onLocationClick; + // Given the input: "scriptLocation:2:100" + // Result: + // ["scriptLocation:2:100", "scriptLocation", "2", "100"] + const locationParts = location.match(/^(.*):(\d+):(\d+)$/); + + if (props.onViewSourceInDebugger && location && locationParts && !IGNORED_SOURCE_URLS.includes(locationParts[1])) { + const [, url, line, column] = locationParts; + onLocationClick = e => { + // Don't trigger ObjectInspector expand/collapse. + e.stopPropagation(); + props.onViewSourceInDebugger({ + url, + line: Number(line), + column: Number(column) + }); + }; + } + + stack.push("\t", span({ + key: `fn${index}`, + className: "objectBox-stackTrace-fn" + }, cleanFunctionName(functionName)), " ", span({ + key: `location${index}`, + className: "objectBox-stackTrace-location", + onClick: onLocationClick, + title: onLocationClick ? `View source in debugger → ${location}` : undefined + }, location), "\n"); + }); + + return span({ + key: "stack", + className: "objectBox-stackTrace-grid" + }, stack); +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(ErrorRep), + supportsObject +}; + +/***/ }), + +/***/ 1794: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(1758); + +const { lengthBubble } = __webpack_require__(1795); +const { + interleave, + getGripType, + isGrip, + wrapRender, + ellipsisElement +} = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); + +const dom = __webpack_require__(1759); +const { span } = dom; +const { ModePropType } = __webpack_require__(1773); +const DEFAULT_TITLE = "Array"; + +/** + * Renders an array. The array is enclosed by left and right bracket + * and the max number of rendered items depends on the current mode. + */ +GripArray.propTypes = { + object: PropTypes.object.isRequired, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: ModePropType, + provider: PropTypes.object, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function GripArray(props) { + const { object, mode = MODE.SHORT } = props; + + let brackets; + const needSpace = function (space) { + return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" }; + }; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-array" + }; + + const title = getTitle(props, object); + + if (mode === MODE.TINY) { + const isEmpty = getLength(object) === 0; + + // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets). + if (!isEmpty && object.class !== "Array") { + return span(config, title); + } + + brackets = needSpace(false); + return span(config, title, span({ + className: "arrayLeftBracket" + }, brackets.left), isEmpty ? null : ellipsisElement, span({ + className: "arrayRightBracket" + }, brackets.right)); + } + + const max = maxLengthMap.get(mode); + const items = arrayIterator(props, object, max); + brackets = needSpace(items.length > 0); + + return span({ + "data-link-actor-id": object.actor, + className: "objectBox objectBox-array" + }, title, span({ + className: "arrayLeftBracket" + }, brackets.left), ...interleave(items, ", "), span({ + className: "arrayRightBracket" + }, brackets.right), span({ + className: "arrayProperties", + role: "group" + })); +} + +function getLength(grip) { + if (!grip.preview) { + return 0; + } + + return grip.preview.length || grip.preview.childNodesLength || 0; +} + +function getTitle(props, object) { + const objectLength = getLength(object); + const isEmpty = objectLength === 0; + + let title = props.title || object.class || DEFAULT_TITLE; + + const length = lengthBubble({ + object, + mode: props.mode, + maxLengthMap, + getLength + }); + + if (props.mode === MODE.TINY) { + if (isEmpty) { + if (object.class === DEFAULT_TITLE) { + return null; + } + + return span({ className: "objectTitle" }, `${title} `); + } + + let trailingSpace; + if (object.class === DEFAULT_TITLE) { + title = null; + trailingSpace = " "; + } + + return span({ className: "objectTitle" }, title, length, trailingSpace); + } + + return span({ className: "objectTitle" }, title, length, " "); +} + +function getPreviewItems(grip) { + if (!grip.preview) { + return null; + } + + return grip.preview.items || grip.preview.childNodes || []; +} + +function arrayIterator(props, grip, max) { + const { Rep } = __webpack_require__(1768); + + let items = []; + const gripLength = getLength(grip); + + if (!gripLength) { + return items; + } + + const previewItems = getPreviewItems(grip); + const provider = props.provider; + + let emptySlots = 0; + let foldedEmptySlots = 0; + items = previewItems.reduce((res, itemGrip) => { + if (res.length >= max) { + return res; + } + + let object; + try { + if (!provider && itemGrip === null) { + emptySlots++; + return res; + } + + object = provider ? provider.getValue(itemGrip) : itemGrip; + } catch (exc) { + object = exc; + } + + if (emptySlots > 0) { + res.push(getEmptySlotsElement(emptySlots)); + foldedEmptySlots = foldedEmptySlots + emptySlots - 1; + emptySlots = 0; + } + + if (res.length < max) { + res.push(Rep(_extends({}, props, { + object, + mode: MODE.TINY, + // Do not propagate title to array items reps + title: undefined + }))); + } + + return res; + }, []); + + // Handle trailing empty slots if there are some. + if (items.length < max && emptySlots > 0) { + items.push(getEmptySlotsElement(emptySlots)); + foldedEmptySlots = foldedEmptySlots + emptySlots - 1; + } + + const itemsShown = items.length + foldedEmptySlots; + if (gripLength > itemsShown) { + items.push(ellipsisElement); + } + + return items; +} + +function getEmptySlotsElement(number) { + // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141 + return `<${number} empty slot${number > 1 ? "s" : ""}>`; +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + + return grip.preview && (grip.preview.kind == "ArrayLike" || getGripType(grip, noGrip) === "DocumentFragment"); +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Exports from this module +module.exports = { + rep: wrapRender(GripArray), + supportsObject, + maxLengthMap, + getLength +}; + +/***/ }), + +/***/ 1795: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const PropTypes = __webpack_require__(1758); + +const { wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); +const { ModePropType } = __webpack_require__(1773); + +const dom = __webpack_require__(1759); +const { span } = dom; + +GripLengthBubble.propTypes = { + object: PropTypes.object.isRequired, + maxLengthMap: PropTypes.instanceOf(Map).isRequired, + getLength: PropTypes.func.isRequired, + mode: ModePropType, + visibilityThreshold: PropTypes.number +}; + +function GripLengthBubble(props) { + const { + object, + mode = MODE.SHORT, + visibilityThreshold = 2, + maxLengthMap, + getLength, + showZeroLength = false + } = props; + + const length = getLength(object); + const isEmpty = length === 0; + const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold; + if (isEmpty && !showZeroLength || isObvious) { + return ""; + } + + return span({ + className: "objectLengthBubble" + }, `(${length})`); +} + +module.exports = { + lengthBubble: wrapRender(GripLengthBubble) +}; + +/***/ }), + +/***/ 1796: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies + +const { lengthBubble } = __webpack_require__(1795); +const PropTypes = __webpack_require__(1758); +const { + interleave, + isGrip, + wrapRender, + ellipsisElement +} = __webpack_require__(1760); +const PropRep = __webpack_require__(1774); +const { MODE } = __webpack_require__(1762); +const { ModePropType } = __webpack_require__(1773); + +const { span } = __webpack_require__(1759); + +/** + * Renders an map. A map is represented by a list of its + * entries enclosed in curly brackets. + */ +GripMap.propTypes = { + object: PropTypes.object, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: ModePropType, + isInterestingEntry: PropTypes.func, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + title: PropTypes.string +}; + +function GripMap(props) { + const { mode, object } = props; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-object" + }; + + const title = getTitle(props, object); + const isEmpty = getLength(object) === 0; + + if (isEmpty || mode === MODE.TINY) { + return span(config, title); + } + + const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode)); + + return span(config, title, span({ + className: "objectLeftBrace" + }, " { "), ...interleave(propsArray, ", "), span({ + className: "objectRightBrace" + }, " }")); +} + +function getTitle(props, object) { + const title = props.title || (object && object.class ? object.class : "Map"); + return span({ + className: "objectTitle" + }, title, lengthBubble({ + object, + mode: props.mode, + maxLengthMap, + getLength, + showZeroLength: true + })); +} + +function safeEntriesIterator(props, object, max) { + max = typeof max === "undefined" ? 3 : max; + try { + return entriesIterator(props, object, max); + } catch (err) { + console.error(err); + } + return []; +} + +function entriesIterator(props, object, max) { + // Entry filter. Show only interesting entries to the user. + const isInterestingEntry = props.isInterestingEntry || ((type, value) => { + return type == "boolean" || type == "number" || type == "string" && value.length != 0; + }); + + const mapEntries = object.preview && object.preview.entries ? object.preview.entries : []; + + let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry); + if (indexes.length < max && indexes.length < mapEntries.length) { + // There are not enough entries yet, so we add uninteresting entries. + indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => { + return !isInterestingEntry(t, value, name); + })); + } + + const entries = getEntries(props, mapEntries, indexes); + if (entries.length < getLength(object)) { + // There are some undisplayed entries. Then display "…". + entries.push(ellipsisElement); + } + + return entries; +} + +/** + * Get entries ordered by index. + * + * @param {Object} props Component props. + * @param {Array} entries Entries array. + * @param {Array} indexes Indexes of entries. + * @return {Array} Array of PropRep. + */ +function getEntries(props, entries, indexes) { + const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props; + + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + return indexes.map((index, i) => { + const [key, entryValue] = entries[index]; + const value = entryValue.value !== undefined ? entryValue.value : entryValue; + + return PropRep({ + name: key, + equal: " \u2192 ", + object: value, + mode: MODE.TINY, + onDOMNodeMouseOver, + onDOMNodeMouseOut, + onInspectIconClick + }); + }); +} + +/** + * Get the indexes of entries in the map. + * + * @param {Array} entries Entries array. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the entry you want. + * @return {Array} Indexes of filtered entries in the map. + */ +function getEntriesIndexes(entries, max, filter) { + return entries.reduce((indexes, [key, entry], i) => { + if (indexes.length < max) { + const value = entry && entry.value !== undefined ? entry.value : entry; + // Type is specified in grip's "class" field and for primitive + // values use typeof. + const type = (value && value.class ? value.class : typeof value).toLowerCase(); + + if (filter(type, value, key)) { + indexes.push(i); + } + } + + return indexes; + }, []); +} + +function getLength(grip) { + return grip.preview.size || 0; +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true || !isGrip(grip)) { + return false; + } + return grip.preview && grip.preview.kind == "MapLike"; +} + +const maxLengthMap = new Map(); +maxLengthMap.set(MODE.SHORT, 3); +maxLengthMap.set(MODE.LONG, 10); + +// Exports from this module +module.exports = { + rep: wrapRender(GripMap), + supportsObject, + maxLengthMap, + getLength +}; + +/***/ }), + +/***/ 1797: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Dependencies +const PropTypes = __webpack_require__(1758); +// Shortcuts +const dom = __webpack_require__(1759); +const { span } = dom; +const { wrapRender } = __webpack_require__(1760); +const PropRep = __webpack_require__(1774); +const { MODE } = __webpack_require__(1762); +/** + * Renders an map entry. A map entry is represented by its key, + * a column and its value. + */ +GripMapEntry.propTypes = { + object: PropTypes.object, + // @TODO Change this to Object.values when supported in Node's version of V8 + mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func +}; + +function GripMapEntry(props) { + const { object } = props; + + const { key, value } = object.preview; + + return span({ + className: "objectBox objectBox-map-entry" + }, PropRep(_extends({}, props, { + name: key, + object: value, + equal: " \u2192 ", + title: null, + suppressQuotes: false + }))); +} + +function supportsObject(grip, noGrip = false) { + if (noGrip === true) { + return false; + } + return grip && (grip.type === "mapEntry" || grip.type === "storageEntry") && grip.preview; +} + +function createGripMapEntry(key, value) { + return { + type: "mapEntry", + preview: { + key, + value + } + }; +} + +// Exports from this module +module.exports = { + rep: wrapRender(GripMapEntry), + createGripMapEntry, + supportsObject +}; + +/***/ }), + +/***/ 1798: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3676,11 +3450,11 @@ var _react = __webpack_require__(0); var _react2 = _interopRequireDefault(_react); -var _reactDomFactories = __webpack_require__(3643); +var _reactDomFactories = __webpack_require__(1759); var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories); -var _propTypes = __webpack_require__(3642); +var _propTypes = __webpack_require__(1758); var _propTypes2 = _interopRequireDefault(_propTypes); @@ -3690,7 +3464,7 @@ const { Component, createFactory } = _react2.default; /* This Source Code Form i * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -__webpack_require__(3671); +__webpack_require__(1799); // depth const AUTO_EXPAND_DEPTH = 0; @@ -4488,21 +4262,338 @@ exports.default = Tree; /***/ }), -/***/ 3671: +/***/ 1799: /***/ (function(module, exports) { // removed by extract-text-webpack-plugin /***/ }), -/***/ 3672: +/***/ 1800: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { + enumEntries, + enumIndexedProperties, + enumNonIndexedProperties, + getPrototype, + enumSymbols, + getFullText +} = __webpack_require__(1801); + +const { + getClosestGripNode, + getClosestNonBucketNode, + getValue, + nodeHasAccessors, + nodeHasAllEntriesInPreview, + nodeHasProperties, + nodeIsBucket, + nodeIsDefaultProperties, + nodeIsEntries, + nodeIsMapEntry, + nodeIsPrimitive, + nodeIsProxy, + nodeNeedsNumericalBuckets, + nodeIsLongString +} = __webpack_require__(1783); + +function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : []; + + const promises = []; + let objectClient; + const getObjectClient = () => objectClient || createObjectClient(value); + + if (shouldLoadItemIndexedProperties(item, loadedProperties)) { + promises.push(enumIndexedProperties(getObjectClient(), start, end)); + } + + if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) { + promises.push(enumNonIndexedProperties(getObjectClient(), start, end)); + } + + if (shouldLoadItemEntries(item, loadedProperties)) { + promises.push(enumEntries(getObjectClient(), start, end)); + } + + if (shouldLoadItemPrototype(item, loadedProperties)) { + promises.push(getPrototype(getObjectClient())); + } + + if (shouldLoadItemSymbols(item, loadedProperties)) { + promises.push(enumSymbols(getObjectClient(), start, end)); + } + + if (shouldLoadItemFullText(item, loadedProperties)) { + promises.push(getFullText(createLongStringClient(value), item)); + } + + return Promise.all(promises).then(mergeResponses); +} + +function mergeResponses(responses) { + const data = {}; + + for (const response of responses) { + if (response.hasOwnProperty("ownProperties")) { + data.ownProperties = _extends({}, data.ownProperties, response.ownProperties); + } + + if (response.ownSymbols && response.ownSymbols.length > 0) { + data.ownSymbols = response.ownSymbols; + } + + if (response.prototype) { + data.prototype = response.prototype; + } + + if (response.fullText) { + data.fullText = response.fullText; + } + } + + return data; +} + +function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeNeedsNumericalBuckets(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && + // The data is loaded when expanding the window node. + !nodeIsDefaultProperties(item); +} + +function shouldLoadItemNonIndexedProperties(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && !nodeIsBucket(item) && + // The data is loaded when expanding the window node. + !nodeIsDefaultProperties(item); +} + +function shouldLoadItemEntries(item, loadedProperties = new Map()) { + const gripItem = getClosestGripNode(item); + const value = getValue(gripItem); + + return value && nodeIsEntries(getClosestNonBucketNode(item)) && !nodeHasAllEntriesInPreview(gripItem) && !loadedProperties.has(item.path) && !nodeNeedsNumericalBuckets(item); +} + +function shouldLoadItemPrototype(item, loadedProperties = new Map()) { + const value = getValue(item); + + return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item); +} + +function shouldLoadItemSymbols(item, loadedProperties = new Map()) { + const value = getValue(item); + + return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item); +} + +function shouldLoadItemFullText(item, loadedProperties = new Map()) { + return !loadedProperties.has(item.path) && nodeIsLongString(item); +} + +module.exports = { + loadItemProperties, + mergeResponses, + shouldLoadItemEntries, + shouldLoadItemIndexedProperties, + shouldLoadItemNonIndexedProperties, + shouldLoadItemPrototype, + shouldLoadItemSymbols, + shouldLoadItemFullText +}; + +/***/ }), + +/***/ 1801: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const { getValue, nodeHasFullText } = __webpack_require__(1783); /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +async function enumIndexedProperties(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumProperties({ + ignoreNonIndexedProperties: true + }); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumIndexedProperties", e); + return {}; + } +} + +async function enumNonIndexedProperties(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumProperties({ + ignoreIndexedProperties: true + }); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumNonIndexedProperties", e); + return {}; + } +} + +async function enumEntries(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumEntries(); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumEntries", e); + return {}; + } +} + +async function enumSymbols(objectClient, start, end) { + try { + const { iterator } = await objectClient.enumSymbols(); + const response = await iteratorSlice(iterator, start, end); + return response; + } catch (e) { + console.error("Error in enumSymbols", e); + return {}; + } +} + +async function getPrototype(objectClient) { + if (typeof objectClient.getPrototype !== "function") { + console.error("objectClient.getPrototype is not a function"); + return Promise.resolve({}); + } + return objectClient.getPrototype(); +} + +async function getFullText(longStringClient, item) { + const { initial, fullText, length } = getValue(item); + + // Return fullText property if it exists so that it can be added to the + // loadedProperties map. + if (nodeHasFullText(item)) { + return Promise.resolve({ fullText }); + } + + return new Promise((resolve, reject) => { + longStringClient.substring(initial.length, length, response => { + if (response.error) { + console.error("LongStringClient.substring", `${response.error}: ${response.message}`); + reject({}); + return; + } + + resolve({ + fullText: initial + response.substring + }); + }); + }); +} + +function iteratorSlice(iterator, start, end) { + start = start || 0; + const count = end ? end - start + 1 : iterator.count; + + if (count === 0) { + return Promise.resolve({}); + } + return iterator.slice(start, count); +} + +module.exports = { + enumEntries, + enumIndexedProperties, + enumNonIndexedProperties, + enumSymbols, + getPrototype, + getFullText +}; + +/***/ }), + +/***/ 1802: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const client = __webpack_require__(1801); +const loadProperties = __webpack_require__(1800); +const node = __webpack_require__(1783); +const { nodeIsError, nodeIsPrimitive } = node; +const selection = __webpack_require__(1852); + +const { MODE } = __webpack_require__(1762); +const { + REPS: { Rep, Grip } +} = __webpack_require__(1768); + + +function shouldRenderRootsInReps(roots) { + if (roots.length > 1) { + return false; + } + + const root = roots[0]; + const name = root && root.name; + return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root)); +} + +function renderRep(item, props) { + return Rep(_extends({}, props, { + object: node.getValue(item), + mode: props.mode || MODE.TINY, + defaultRep: Grip + })); +} + +module.exports = { + client, + loadProperties, + node, + renderRep, + selection, + shouldRenderRootsInReps +}; + +/***/ }), + +/***/ 1824: /***/ (function(module, exports) { // removed by extract-text-webpack-plugin /***/ }), -/***/ 3673: +/***/ 1825: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4513,9 +4604,9 @@ exports.default = Tree; * file, You can obtain one at . */ // Dependencies -const { getGripType, wrapRender } = __webpack_require__(3644); +const { getGripType, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4542,7 +4633,7 @@ module.exports = { /***/ }), -/***/ 3674: +/***/ 1826: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4553,8 +4644,8 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const { wrapRender } = __webpack_require__(3644); -const dom = __webpack_require__(3643); +const { wrapRender } = __webpack_require__(1760); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4585,7 +4676,7 @@ module.exports = { /***/ }), -/***/ 3675: +/***/ 1827: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4596,11 +4687,11 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); -const { getGripType, wrapRender } = __webpack_require__(3644); +const { getGripType, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4635,7 +4726,7 @@ module.exports = { /***/ }), -/***/ 3676: +/***/ 1828: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4648,12 +4739,12 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); -const { wrapRender, ellipsisElement } = __webpack_require__(3644); -const PropRep = __webpack_require__(3650); -const { MODE } = __webpack_require__(3645); +const PropTypes = __webpack_require__(1758); +const { wrapRender, ellipsisElement } = __webpack_require__(1760); +const PropRep = __webpack_require__(1774); +const { MODE } = __webpack_require__(1762); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; const DEFAULT_TITLE = "Object"; @@ -4807,7 +4898,7 @@ module.exports = { /***/ }), -/***/ 3677: +/***/ 1829: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4818,11 +4909,11 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); -const { getGripType, wrapRender } = __webpack_require__(3644); +const { getGripType, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4854,7 +4945,7 @@ module.exports = { /***/ }), -/***/ 3678: +/***/ 1830: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4865,11 +4956,11 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); -const { getGripType, wrapRender } = __webpack_require__(3644); +const { getGripType, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4898,7 +4989,7 @@ module.exports = { /***/ }), -/***/ 3679: +/***/ 1831: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4909,9 +5000,9 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const { getGripType, wrapRender } = __webpack_require__(3644); +const { getGripType, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -4933,7 +5024,7 @@ module.exports = { /***/ }), -/***/ 3680: +/***/ 1832: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4944,10 +5035,10 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const dom = __webpack_require__(3643); -const PropTypes = __webpack_require__(3642); -const { wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); +const dom = __webpack_require__(1759); +const PropTypes = __webpack_require__(1758); +const { wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); const { span } = dom; /** @@ -4998,7 +5089,7 @@ module.exports = { /***/ }), -/***/ 3681: +/***/ 1833: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5009,13 +5100,135 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); -const dom = __webpack_require__(3643); +const PropTypes = __webpack_require__(1758); +const { button, span } = __webpack_require__(1759); + +// Utils +const { isGrip, wrapRender } = __webpack_require__(1760); +const { rep: StringRep } = __webpack_require__(1769); + +/** + * Renders Accessible object. + */ +Accessible.propTypes = { + object: PropTypes.object.isRequired, + inspectIconTitle: PropTypes.string, + nameMaxLength: PropTypes.number, + onAccessibleClick: PropTypes.func, + onAccessibleMouseOver: PropTypes.func, + onAccessibleMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + separatorText: PropTypes.string +}; + +function Accessible(props) { + const { + object, + inspectIconTitle, + nameMaxLength, + onAccessibleClick, + onAccessibleMouseOver, + onAccessibleMouseOut, + onInspectIconClick, + separatorText + } = props; + const elements = getElements(object, nameMaxLength, separatorText); + const isInTree = object.preview && object.preview.isConnected === true; + const baseConfig = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-accessible" + }; + + let inspectIcon; + if (isInTree) { + if (onAccessibleClick) { + Object.assign(baseConfig, { + onClick: _ => onAccessibleClick(object), + className: `${baseConfig.className} clickable` + }); + } + + if (onAccessibleMouseOver) { + Object.assign(baseConfig, { + onMouseOver: _ => onAccessibleMouseOver(object) + }); + } + + if (onAccessibleMouseOut) { + Object.assign(baseConfig, { + onMouseOut: onAccessibleMouseOut + }); + } + + if (onInspectIconClick) { + inspectIcon = button({ + className: "open-accessibility-inspector", + title: inspectIconTitle, + onClick: e => { + if (onAccessibleClick) { + e.stopPropagation(); + } + + onInspectIconClick(object, e); + } + }); + } + } + + return span(baseConfig, ...elements, inspectIcon); +} + +function getElements(grip, nameMaxLength, separatorText = ": ") { + const { name, role } = grip.preview; + const elements = []; + + elements.push(span({ className: "accessible-role" }, role)); + if (name) { + elements.push(span({ className: "separator" }, separatorText), StringRep({ + className: "accessible-name", + object: name, + cropLimit: nameMaxLength + })); + } + + return elements; +} + +// Registration +function supportsObject(object, noGrip = false) { + if (noGrip === true || !isGrip(object)) { + return false; + } + + return object.preview && object.typeName && object.typeName === "accessible"; +} + +// Exports from this module +module.exports = { + rep: wrapRender(Accessible), + supportsObject +}; + +/***/ }), + +/***/ 1834: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// ReactJS +const PropTypes = __webpack_require__(1758); +const dom = __webpack_require__(1759); const { span } = dom; // Reps -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); -const { rep: StringRep } = __webpack_require__(3648); +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); +const { rep: StringRep } = __webpack_require__(1769); /** * Renders DOM attribute @@ -5054,7 +5267,7 @@ module.exports = { /***/ }), -/***/ 3682: +/***/ 1835: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5065,12 +5278,12 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5118,7 +5331,7 @@ module.exports = { /***/ }), -/***/ 3683: +/***/ 1836: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5129,7 +5342,7 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps const { @@ -5137,9 +5350,9 @@ const { isGrip, getURLDisplayString, wrapRender -} = __webpack_require__(3644); +} = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5187,7 +5400,7 @@ module.exports = { /***/ }), -/***/ 3684: +/***/ 1837: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5198,11 +5411,11 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); -const dom = __webpack_require__(3643); +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5239,7 +5452,7 @@ module.exports = { /***/ }), -/***/ 3685: +/***/ 1838: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5252,13 +5465,13 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { isGrip, wrapRender } = __webpack_require__(3644); +const { isGrip, wrapRender } = __webpack_require__(1760); -const { MODE } = __webpack_require__(3645); -const { rep } = __webpack_require__(3656); +const { MODE } = __webpack_require__(1762); +const { rep } = __webpack_require__(1782); /** * Renders DOM event objects. @@ -5345,7 +5558,7 @@ module.exports = { /***/ }), -/***/ 3686: +/***/ 1839: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5358,14 +5571,14 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Dependencies -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); -const PropRep = __webpack_require__(3650); -const { MODE } = __webpack_require__(3645); +const PropRep = __webpack_require__(1774); +const { MODE } = __webpack_require__(1762); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5390,7 +5603,7 @@ function PromiseRep(props) { }; if (props.mode === MODE.TINY) { - const { Rep } = __webpack_require__(3647); + const { Rep } = __webpack_require__(1768); return span(config, getTitle(object), span({ className: "objectLeftBrace" @@ -5454,7 +5667,7 @@ module.exports = { /***/ }), -/***/ 3687: +/***/ 1840: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5465,12 +5678,12 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { getGripType, isGrip, wrapRender } = __webpack_require__(3644); +const { getGripType, isGrip, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5510,7 +5723,7 @@ module.exports = { /***/ }), -/***/ 3688: +/***/ 1841: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5521,7 +5734,7 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps const { @@ -5529,9 +5742,9 @@ const { isGrip, getURLDisplayString, wrapRender -} = __webpack_require__(3644); +} = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5579,7 +5792,7 @@ module.exports = { /***/ }), -/***/ 3689: +/***/ 1842: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5590,16 +5803,16 @@ module.exports = { * file, You can obtain one at . */ // Dependencies -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); const { isGrip, cropString, cropMultipleLines, wrapRender -} = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); -const nodeConstants = __webpack_require__(3659); -const dom = __webpack_require__(3643); +} = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); +const nodeConstants = __webpack_require__(1792); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5643,7 +5856,7 @@ module.exports = { /***/ }), -/***/ 3690: +/***/ 1843: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5654,15 +5867,15 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Utils -const { isGrip, wrapRender } = __webpack_require__(3644); -const { rep: StringRep } = __webpack_require__(3648); -const { MODE } = __webpack_require__(3645); -const nodeConstants = __webpack_require__(3659); +const { isGrip, wrapRender } = __webpack_require__(1760); +const { rep: StringRep } = __webpack_require__(1769); +const { MODE } = __webpack_require__(1762); +const nodeConstants = __webpack_require__(1792); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5798,7 +6011,7 @@ module.exports = { /***/ }), -/***/ 3691: +/***/ 1844: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5809,13 +6022,13 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { isGrip, cropString, wrapRender } = __webpack_require__(3644); -const { MODE } = __webpack_require__(3645); +const { isGrip, cropString, wrapRender } = __webpack_require__(1760); +const { MODE } = __webpack_require__(1762); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5903,7 +6116,7 @@ module.exports = { /***/ }), -/***/ 3692: +/***/ 1845: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5914,7 +6127,7 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps const { @@ -5922,11 +6135,11 @@ const { isGrip, getURLDisplayString, wrapRender -} = __webpack_require__(3644); +} = __webpack_require__(1760); -const { MODE } = __webpack_require__(3645); +const { MODE } = __webpack_require__(1762); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -5982,7 +6195,7 @@ module.exports = { /***/ }), -/***/ 3693: +/***/ 1846: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -5993,14 +6206,14 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { isGrip, wrapRender } = __webpack_require__(3644); +const { isGrip, wrapRender } = __webpack_require__(1760); -const String = __webpack_require__(3648).rep; +const String = __webpack_require__(1769).rep; -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -6045,7 +6258,7 @@ module.exports = { /***/ }), -/***/ 3694: +/***/ 1847: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6056,12 +6269,12 @@ module.exports = { * file, You can obtain one at . */ // ReactJS -const PropTypes = __webpack_require__(3642); +const PropTypes = __webpack_require__(1758); // Reps -const { isGrip, getURLDisplayString, wrapRender } = __webpack_require__(3644); +const { isGrip, getURLDisplayString, wrapRender } = __webpack_require__(1760); -const dom = __webpack_require__(3643); +const dom = __webpack_require__(1759); const { span } = dom; /** @@ -6108,7 +6321,7 @@ module.exports = { /***/ }), -/***/ 3695: +/***/ 1848: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6118,39 +6331,15 @@ module.exports = { * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -const { createElement, createFactory, PureComponent } = __webpack_require__(0); -const { Provider } = __webpack_require__(3592); -const ObjectInspector = createFactory(__webpack_require__(3696)); -const createStore = __webpack_require__(3700); -const Utils = __webpack_require__(3657); -const { renderRep, shouldRenderRootsInReps } = Utils; +const ObjectInspector = __webpack_require__(1849); +const utils = __webpack_require__(1802); +const reducer = __webpack_require__(1784); -class OI extends PureComponent { - constructor(props) { - super(props); - this.store = createStore(props); - } - - getStore() { - return this.store; - } - - render() { - return createElement(Provider, { store: this.store }, ObjectInspector(this.props)); - } -} - -module.exports = props => { - const { roots } = props; - if (shouldRenderRootsInReps(roots)) { - return renderRep(roots[0], props); - } - return new OI(props); -}; +module.exports = { ObjectInspector, utils, reducer }; /***/ }), -/***/ 3696: +/***/ 1849: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6162,7 +6351,7 @@ var _devtoolsServices = __webpack_require__(22); var _devtoolsServices2 = _interopRequireDefault(_devtoolsServices); -var _devtoolsComponents = __webpack_require__(3669); +var _devtoolsComponents = __webpack_require__(1788); var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents); @@ -6172,25 +6361,28 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -const { Component, createFactory } = __webpack_require__(0); -const dom = __webpack_require__(3643); -const { connect } = __webpack_require__(3592); -const { bindActionCreators } = __webpack_require__(3593); +const { Component, createFactory, createElement } = __webpack_require__(0); +const dom = __webpack_require__(1759); +const { connect } = __webpack_require__(1763); +const actions = __webpack_require__(1850); + +const selectors = __webpack_require__(1784); const { appinfo } = _devtoolsServices2.default; const isMacOS = appinfo.OS === "Darwin"; const Tree = createFactory(_devtoolsComponents2.default.Tree); -__webpack_require__(3697); +__webpack_require__(1851); const classnames = __webpack_require__(175); -const { MODE } = __webpack_require__(3645); +const { MODE } = __webpack_require__(1762); -const Utils = __webpack_require__(3657); +const Utils = __webpack_require__(1802); +const { renderRep, shouldRenderRootsInReps } = Utils; const { getChildren, - getClosestGripNode, + getActor, getParent, getValue, nodeHasAccessors, @@ -6254,28 +6446,28 @@ class ObjectInspector extends Component { self.getRoots = this.getRoots.bind(this); } - shouldComponentUpdate(nextProps) { - const { expandedPaths, focusedItem, loadedProperties, roots } = this.props; + componentWillMount() { + this.roots = this.props.roots; + this.focusedItem = this.props.focusedItem; + } - if (roots !== nextProps.roots) { + componentWillUpdate(nextProps) { + if (this.roots !== nextProps.roots) { // Since the roots changed, we assume the properties did as well, // so we need to cleanup the component internal state. // We can clear the cachedNodes to avoid bugs and memory leaks. this.cachedNodes.clear(); - // The rootsChanged action will be handled in a middleware to release the - // actors of the old roots, as well as cleanup the state properties - // (expandedPaths, loadedProperties, …). - this.props.rootsChanged(nextProps); - // We don't render right away since the state is going to be changed by - // the rootsChanged action. The `state.forceUpdate` flag will be set - // to `true` so we can execute a new render cycle with the cleaned state. - return false; + this.roots = nextProps.roots; + this.focusedItem = nextProps.focusedItem; + if (this.props.rootsChanged) { + this.props.rootsChanged(); + } } + } - if (nextProps.forceUpdate === true) { - return true; - } + shouldComponentUpdate(nextProps) { + const { expandedPaths, loadedProperties } = this.props; // We should update if: // - there are new loaded properties @@ -6284,26 +6476,11 @@ class ObjectInspector extends Component { // - OR the expanded paths number did not changed, but old and new sets // differ // - OR the focused node changed. - return loadedProperties.size !== nextProps.loadedProperties.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || focusedItem !== nextProps.focusedItem; - } - - componentDidUpdate(prevProps) { - if (this.props.forceUpdate) { - // If the component was updated, we can then reset the forceUpdate flag. - this.props.forceUpdated(); - } + return loadedProperties.size !== nextProps.loadedProperties.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || this.focusedItem !== nextProps.focusedItem || this.roots !== nextProps.roots; } componentWillUnmount() { - const { releaseActor } = this.props; - if (typeof releaseActor !== "function") { - return; - } - - const { actors } = this.props; - for (const actor of actors) { - releaseActor(actor); - } + this.props.closeObjectInspector(); } getItemChildren(item) { @@ -6331,9 +6508,6 @@ class ObjectInspector extends Component { } const { - createObjectClient, - createLongStringClient, - loadedProperties, nodeExpand, nodeCollapse, recordTelemetryEvent, @@ -6341,15 +6515,8 @@ class ObjectInspector extends Component { } = this.props; if (expand === true) { - const gripItem = getClosestGripNode(item); - const value = getValue(gripItem); - const isRoot = value && roots.some(root => { - const rootValue = getValue(root); - return rootValue && rootValue.actor === value.actor; - }); - const actor = isRoot || !value ? null : value.actor; - nodeExpand(item, actor, loadedProperties, createObjectClient, createLongStringClient); - + const actor = getActor(item, roots); + nodeExpand(item, actor); if (recordTelemetryEvent) { recordTelemetryEvent("object_expanded"); } @@ -6359,11 +6526,13 @@ class ObjectInspector extends Component { } focusItem(item) { - const { focusable = true, focusedItem, nodeFocus, onFocus } = this.props; + const { focusable = true, onFocus } = this.props; - if (focusable && focusedItem !== item) { - nodeFocus(item); - if (focusedItem !== item && onFocus) { + if (focusable && this.focusedItem !== item) { + this.focusedItem = item; + this.forceUpdate(); + + if (onFocus) { onFocus(item); } } @@ -6535,7 +6704,6 @@ class ObjectInspector extends Component { focusable = true, disableWrap = false, expandedPaths, - focusedItem, inline } = this.props; @@ -6545,12 +6713,13 @@ class ObjectInspector extends Component { nowrap: disableWrap, "object-inspector": true }), + autoExpandAll, autoExpandDepth, isExpanded: item => expandedPaths && expandedPaths.has(item.path), isExpandable: item => nodeIsPrimitive(item) === false, - focused: focusedItem, + focused: this.focusedItem, getRoots: this.getRoots, getParent, @@ -6568,31 +6737,134 @@ class ObjectInspector extends Component { function mapStateToProps(state, props) { return { - actors: state.actors, - expandedPaths: state.expandedPaths, - // If the root changes, we want to pass a possibly new focusedItem property - focusedItem: state.roots !== props.roots ? props.focusedItem : state.focusedItem, - loadedProperties: state.loadedProperties, - forceUpdate: state.forceUpdate + actors: selectors.getActors(state), + expandedPaths: selectors.getExpandedPaths(state), + loadedProperties: selectors.getLoadedProperties(state) }; } -function mapDispatchToProps(dispatch) { - return bindActionCreators(__webpack_require__(3699), dispatch); -} +const OI = connect(mapStateToProps, actions)(ObjectInspector); -module.exports = connect(mapStateToProps, mapDispatchToProps)(ObjectInspector); +module.exports = props => { + const { roots } = props; + if (shouldRenderRootsInReps(roots)) { + return renderRep(roots[0], props); + } + + return createElement(OI, props); +}; /***/ }), -/***/ 3697: +/***/ 1850: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const { loadItemProperties } = __webpack_require__(1800); /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +const { getLoadedProperties, getActors } = __webpack_require__(1784); + +/** + * This action is responsible for expanding a given node, which also means that + * it will call the action responsible to fetch properties. + */ +function nodeExpand(node, actor) { + return async ({ dispatch, getState }) => { + dispatch({ type: "NODE_EXPAND", data: { node } }); + dispatch(nodeLoadProperties(node, actor)); + }; +} + +function nodeCollapse(node) { + return { + type: "NODE_COLLAPSE", + data: { node } + }; +} + +/* + * This action checks if we need to fetch properties, entries, prototype and + * symbols for a given node. If we do, it will call the appropriate ObjectClient + * functions. + */ +function nodeLoadProperties(node, actor) { + return async ({ dispatch, client, getState }) => { + const loadedProperties = getLoadedProperties(getState()); + if (loadedProperties.has(node.path)) { + return; + } + + try { + const properties = await loadItemProperties(node, client.createObjectClient, client.createLongStringClient, loadedProperties); + + dispatch(nodePropertiesLoaded(node, actor, properties)); + } catch (e) { + console.error(e); + } + }; +} + +function nodePropertiesLoaded(node, actor, properties) { + return { + type: "NODE_PROPERTIES_LOADED", + data: { node, actor, properties } + }; +} + +function closeObjectInspector() { + return async ({ getState, client }) => { + releaseActors(getState(), client); + }; +} + +/* + * This action is dispatched when the `roots` prop, provided by a consumer of + * the ObjectInspector (inspector, console, …), is modified. It will clean the + * internal state properties (expandedPaths, loadedProperties, …) and release + * the actors consumed with the previous roots. + * It takes a props argument which reflects what is passed by the upper-level + * consumer. + */ +function rootsChanged(props) { + return async ({ dispatch, client, getState }) => { + releaseActors(getState(), client); + dispatch({ + type: "ROOTS_CHANGED", + data: props + }); + }; +} + +function releaseActors(state, client) { + const actors = getActors(state); + for (const actor of actors) { + client.releaseActor(actor); + } +} + +module.exports = { + closeObjectInspector, + nodeExpand, + nodeCollapse, + nodeLoadProperties, + nodePropertiesLoaded, + rootsChanged +}; + +/***/ }), + +/***/ 1851: /***/ (function(module, exports) { // removed by extract-text-webpack-plugin /***/ }), -/***/ 3698: +/***/ 1852: /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -6617,455 +6889,18 @@ module.exports = { /***/ }), -/***/ 3699: +/***/ 2082: /***/ (function(module, exports, __webpack_require__) { -"use strict"; - - -const { loadItemProperties } = __webpack_require__(3666); /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -/** - * This action is responsible for expanding a given node, which also means that - * it will call the action responsible to fetch properties. - */ -function nodeExpand(node, actor, loadedProperties, createObjectClient, createLongStringClient) { - return async ({ dispatch }) => { - dispatch({ - type: "NODE_EXPAND", - data: { node } - }); - - if (!loadedProperties.has(node.path)) { - dispatch(nodeLoadProperties(node, actor, loadedProperties, createObjectClient, createLongStringClient)); - } - }; -} - -function nodeCollapse(node) { - return { - type: "NODE_COLLAPSE", - data: { node } - }; -} - -function nodeFocus(node) { - return { - type: "NODE_FOCUS", - data: { node } - }; -} -/* - * This action checks if we need to fetch properties, entries, prototype and - * symbols for a given node. If we do, it will call the appropriate ObjectClient - * functions. - */ -function nodeLoadProperties(item, actor, loadedProperties, createObjectClient, createLongStringClient) { - return async ({ dispatch }) => { - try { - const properties = await loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties); - dispatch(nodePropertiesLoaded(item, actor, properties)); - } catch (e) { - console.error(e); - } - }; -} - -function nodePropertiesLoaded(node, actor, properties) { - return { - type: "NODE_PROPERTIES_LOADED", - data: { node, actor, properties } - }; -} - -/* - * This action is dispatched when the `roots` prop, provided by a consumer of - * the ObjectInspector (inspector, console, …), is modified. It will clean the - * internal state properties (expandedPaths, loadedProperties, …) and release - * the actors consumed with the previous roots. - * It takes a props argument which reflects what is passed by the upper-level - * consumer. - */ -function rootsChanged(props) { - return { - type: "ROOTS_CHANGED", - data: props - }; -} - -/* - * This action will reset the `forceUpdate` flag in the state. - */ -function forceUpdated() { - return { - type: "FORCE_UPDATED" - }; -} - -module.exports = { - forceUpdated, - nodeExpand, - nodeCollapse, - nodeFocus, - nodeLoadProperties, - nodePropertiesLoaded, - rootsChanged -}; - -/***/ }), - -/***/ 3700: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -const { applyMiddleware, createStore, compose } = __webpack_require__(3593); -const { thunk } = __webpack_require__(3701); -const { - waitUntilService -} = __webpack_require__(3702); -const reducer = __webpack_require__(3703); - -function createInitialState(overrides) { - return _extends({ - actors: new Set(), - expandedPaths: new Set(), - focusedItem: null, - loadedProperties: new Map(), - forceUpdated: false - }, overrides); -} - -function enableStateReinitializer(props) { - return next => (innerReducer, initialState, enhancer) => { - function reinitializerEnhancer(state, action) { - if (action.type !== "ROOTS_CHANGED") { - return innerReducer(state, action); - } - - if (props.releaseActor && initialState.actors) { - initialState.actors.forEach(props.releaseActor); - } - - return _extends({}, action.data, { - actors: new Set(), - expandedPaths: new Set(), - loadedProperties: new Map(), - // Indicates to the component that we do want to render on the next - // render cycle. - forceUpdate: true - }); - } - return next(reinitializerEnhancer, initialState, enhancer); - }; -} - -module.exports = props => { - const middlewares = [thunk]; - - if (props.injectWaitService) { - middlewares.push(waitUntilService); - } - - return createStore(reducer, createInitialState(props), compose(applyMiddleware(...middlewares), enableStateReinitializer(props))); -}; - -/***/ }), - -/***/ 3701: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -/** - * A middleware that allows thunks (functions) to be dispatched. - * If it's a thunk, it is called with `dispatch` and `getState`, - * allowing the action to create multiple actions (most likely - * asynchronously). - */ -function thunk({ dispatch, getState }) { - return next => action => { - return typeof action === "function" ? action({ dispatch, getState }) : next(action); - }; -} -exports.thunk = thunk; - -/***/ }), - -/***/ 3702: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -const WAIT_UNTIL_TYPE = "@@service/waitUntil"; -/** - * A middleware which acts like a service, because it is stateful - * and "long-running" in the background. It provides the ability - * for actions to install a function to be run once when a specific - * condition is met by an action coming through the system. Think of - * it as a thunk that blocks until the condition is met. Example: - * - * ```js - * const services = { WAIT_UNTIL: require('wait-service').NAME }; - * - * { type: services.WAIT_UNTIL, - * predicate: action => action.type === "ADD_ITEM", - * run: (dispatch, getState, action) => { - * // Do anything here. You only need to accept the arguments - * // if you need them. `action` is the action that satisfied - * // the predicate. - * } - * } - * ``` - */ -function waitUntilService({ dispatch, getState }) { - let pending = []; - - function checkPending(action) { - const readyRequests = []; - const stillPending = []; - - // Find the pending requests whose predicates are satisfied with - // this action. Wait to run the requests until after we update the - // pending queue because the request handler may synchronously - // dispatch again and run this service (that use case is - // completely valid). - for (const request of pending) { - if (request.predicate(action)) { - readyRequests.push(request); - } else { - stillPending.push(request); - } - } - - pending = stillPending; - for (const request of readyRequests) { - request.run(dispatch, getState, action); - } - } - - return next => action => { - if (action.type === WAIT_UNTIL_TYPE) { - pending.push(action); - return null; - } - const result = next(action); - checkPending(action); - return result; - }; -} - -module.exports = { - WAIT_UNTIL_TYPE, - waitUntilService -}; - -/***/ }), - -/***/ 3703: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -function reducer(state = {}, action) { - const { type, data } = action; - - const cloneState = overrides => _extends({}, state, overrides); - - if (type === "NODE_EXPAND") { - return cloneState({ - expandedPaths: new Set(state.expandedPaths).add(data.node.path) - }); - } - - if (type === "NODE_COLLAPSE") { - const expandedPaths = new Set(state.expandedPaths); - expandedPaths.delete(data.node.path); - return cloneState({ expandedPaths }); - } - - if (type === "NODE_PROPERTIES_LOADED") { - return cloneState({ - actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors, - loadedProperties: new Map(state.loadedProperties).set(data.node.path, action.data.properties) - }); - } - - if (type === "NODE_FOCUS") { - if (state.focusedItem === data.node) { - return state; - } - - return cloneState({ - focusedItem: data.node - }); - } - - if (type === "FORCE_UPDATED") { - return cloneState({ - forceUpdate: false - }); - } - - return state; -} /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - - -module.exports = reducer; - -/***/ }), - -/***/ 3730: -/***/ (function(module, exports, __webpack_require__) { - -module.exports = __webpack_require__(3655); +module.exports = __webpack_require__(1778); /***/ }), -/***/ 3787: -/***/ (function(module, exports, __webpack_require__) { +/***/ 22: +/***/ (function(module, exports) { -"use strict"; - - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// ReactJS -const PropTypes = __webpack_require__(3642); -const { button, span } = __webpack_require__(3643); - -// Utils -const { isGrip, wrapRender } = __webpack_require__(3644); -const { rep: StringRep } = __webpack_require__(3648); - -/** - * Renders Accessible object. - */ -Accessible.propTypes = { - object: PropTypes.object.isRequired, - inspectIconTitle: PropTypes.string, - nameMaxLength: PropTypes.number, - onAccessibleClick: PropTypes.func, - onAccessibleMouseOver: PropTypes.func, - onAccessibleMouseOut: PropTypes.func, - onInspectIconClick: PropTypes.func, - separatorText: PropTypes.string -}; - -function Accessible(props) { - const { - object, - inspectIconTitle, - nameMaxLength, - onAccessibleClick, - onAccessibleMouseOver, - onAccessibleMouseOut, - onInspectIconClick, - separatorText - } = props; - const elements = getElements(object, nameMaxLength, separatorText); - const isInTree = object.preview && object.preview.isConnected === true; - const baseConfig = { - "data-link-actor-id": object.actor, - className: "objectBox objectBox-accessible" - }; - - let inspectIcon; - if (isInTree) { - if (onAccessibleClick) { - Object.assign(baseConfig, { - onClick: _ => onAccessibleClick(object), - className: `${baseConfig.className} clickable` - }); - } - - if (onAccessibleMouseOver) { - Object.assign(baseConfig, { - onMouseOver: _ => onAccessibleMouseOver(object) - }); - } - - if (onAccessibleMouseOut) { - Object.assign(baseConfig, { - onMouseOut: onAccessibleMouseOut - }); - } - - if (onInspectIconClick) { - inspectIcon = button({ - className: "open-accessibility-inspector", - title: inspectIconTitle, - onClick: e => { - if (onAccessibleClick) { - e.stopPropagation(); - } - - onInspectIconClick(object, e); - } - }); - } - } - - return span(baseConfig, ...elements, inspectIcon); -} - -function getElements(grip, nameMaxLength, separatorText = ": ") { - const { name, role } = grip.preview; - const elements = []; - - elements.push(span({ className: "accessible-role" }, role)); - if (name) { - elements.push(span({ className: "separator" }, separatorText), StringRep({ - className: "accessible-name", - object: name, - cropLimit: nameMaxLength - })); - } - - return elements; -} - -// Registration -function supportsObject(object, noGrip = false) { - if (noGrip === true || !isGrip(object)) { - return false; - } - - return object.preview && object.typeName && object.typeName === "accessible"; -} - -// Exports from this module -module.exports = { - rep: wrapRender(Accessible), - supportsObject -}; +module.exports = __WEBPACK_EXTERNAL_MODULE_22__; /***/ }) diff --git a/devtools/client/webconsole/actions/filters.js b/devtools/client/webconsole/actions/filters.js index 9f86a09002d5..61423907d6f0 100644 --- a/devtools/client/webconsole/actions/filters.js +++ b/devtools/client/webconsole/actions/filters.js @@ -26,7 +26,7 @@ function filterTextSet(text) { } function filterToggle(filter) { - return (dispatch, getState, {prefsService}) => { + return ({dispatch, getState, prefsService}) => { dispatch({ type: FILTER_TOGGLE, filter, @@ -37,7 +37,7 @@ function filterToggle(filter) { } function filtersClear() { - return (dispatch, getState, {prefsService}) => { + return ({dispatch, getState, prefsService}) => { dispatch({ type: FILTERS_CLEAR, }); @@ -58,7 +58,7 @@ function filtersClear() { * to keep non-default filters the user might have set. */ function defaultFiltersReset() { - return (dispatch, getState, {prefsService}) => { + return ({dispatch, getState, prefsService}) => { // Get the state before dispatching so the action does not alter prefs reset. const filterState = getAllFilters(getState()); diff --git a/devtools/client/webconsole/actions/messages.js b/devtools/client/webconsole/actions/messages.js index c2083cc2276e..ebd3d964f310 100644 --- a/devtools/client/webconsole/actions/messages.js +++ b/devtools/client/webconsole/actions/messages.js @@ -78,7 +78,7 @@ function messageClose(id) { } function messageTableDataGet(id, client, dataType) { - return (dispatch) => { + return ({dispatch}) => { let fetchObjectActorData; if (["Map", "WeakMap", "Set", "WeakSet"].includes(dataType)) { fetchObjectActorData = (cb) => client.enumEntries(cb); diff --git a/devtools/client/webconsole/actions/ui.js b/devtools/client/webconsole/actions/ui.js index 0d0f6f8b743b..ab2eb711d140 100644 --- a/devtools/client/webconsole/actions/ui.js +++ b/devtools/client/webconsole/actions/ui.js @@ -23,7 +23,7 @@ const { } = require("devtools/client/webconsole/constants"); function filterBarToggle() { - return (dispatch, getState, {prefsService}) => { + return ({dispatch, getState, prefsService}) => { dispatch({ type: FILTER_BAR_TOGGLE, }); @@ -33,7 +33,7 @@ function filterBarToggle() { } function persistToggle() { - return (dispatch, getState, {prefsService}) => { + return ({dispatch, getState, prefsService}) => { dispatch({ type: PERSIST_TOGGLE, }); @@ -83,7 +83,7 @@ function splitConsoleCloseButtonToggle(shouldDisplayButton) { * @param {String} messageId: id of the message containing the {actor} parameter. */ function showMessageObjectInSidebar(actor, messageId) { - return (dispatch, getState) => { + return ({dispatch, getState}) => { const { parameters } = getMessage(getState(), messageId); if (Array.isArray(parameters)) { for (const parameter of parameters) { diff --git a/devtools/client/webconsole/components/GripMessageBody.js b/devtools/client/webconsole/components/GripMessageBody.js index aef6af612f26..061f76a8cbbf 100644 --- a/devtools/client/webconsole/components/GripMessageBody.js +++ b/devtools/client/webconsole/components/GripMessageBody.js @@ -16,7 +16,7 @@ const { getObjectInspector } = require("devtools/client/webconsole/utils/object- const actions = require("devtools/client/webconsole/actions/index"); const reps = require("devtools/client/shared/components/reps/reps"); -const { MODE, ObjectInspectorUtils } = reps; +const { MODE, objectInspector: {utils} } = reps; GripMessageBody.displayName = "GripMessageBody"; @@ -66,7 +66,7 @@ function GripMessageBody(props) { // fixed the issue (See Bug 1456060). focusable: false, onCmdCtrlClick: (node, { depth, event, focused, expanded }) => { - const value = ObjectInspectorUtils.node.getValue(node); + const value = utils.node.getValue(node); if (value) { dispatch(actions.showObjectInSidebar(value)); } diff --git a/devtools/client/webconsole/middleware/thunk.js b/devtools/client/webconsole/middleware/thunk.js index 14252c4250f6..5a6e6d42d48b 100644 --- a/devtools/client/webconsole/middleware/thunk.js +++ b/devtools/client/webconsole/middleware/thunk.js @@ -10,7 +10,7 @@ function thunk(options = {}, { dispatch, getState }) { return next => action => { return (typeof action === "function") - ? action(dispatch, getState, options) + ? action({dispatch, getState, ...options}) : next(action); }; } diff --git a/devtools/client/webconsole/reducers/index.js b/devtools/client/webconsole/reducers/index.js index a92d41029dcc..7cdf60c4c21c 100644 --- a/devtools/client/webconsole/reducers/index.js +++ b/devtools/client/webconsole/reducers/index.js @@ -12,6 +12,8 @@ const { ui } = require("./ui"); const { notifications } = require("./notifications"); const { history } = require("./history"); +const { objectInspector } = require("devtools/client/shared/components/reps/reps.js"); + exports.reducers = { filters, messages, @@ -19,4 +21,5 @@ exports.reducers = { ui, notifications, history, + objectInspector: objectInspector.reducer.default }; diff --git a/devtools/client/webconsole/store.js b/devtools/client/webconsole/store.js index 9b63fbfbda70..03bc2964b22f 100644 --- a/devtools/client/webconsole/store.js +++ b/devtools/client/webconsole/store.js @@ -79,7 +79,7 @@ function configureStore(hud, options = {}) { // Prepare middleware. const middleware = applyMiddleware( - thunk.bind(null, {prefsService}), + thunk.bind(null, {prefsService, client: (options.services || {})}), historyPersistence, eventTelemetry.bind(null, options.telemetry, options.sessionId), ); diff --git a/devtools/client/webconsole/test/components/console-api-call.test.js b/devtools/client/webconsole/test/components/console-api-call.test.js index 86da2482330e..3c78fa142426 100644 --- a/devtools/client/webconsole/test/components/console-api-call.test.js +++ b/devtools/client/webconsole/test/components/console-api-call.test.js @@ -223,7 +223,11 @@ describe("ConsoleAPICall component:", () => { it("renders", () => { const message = stubPreparedMessages.get( "console.assert(false, {message: 'foobar'})"); - const wrapper = render(ConsoleApiCall({ message, serviceContainer })); + + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, + ConsoleApiCall({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()) .toBe("Assertion failed: Object { message: \"foobar\" }"); @@ -248,13 +252,19 @@ describe("ConsoleAPICall component:", () => { describe("console.timeLog", () => { it("renders as expected", () => { let message = stubPreparedMessages.get("console.timeLog('bar') - 1"); - let wrapper = render(ConsoleApiCall({ message, serviceContainer })); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + let wrapper = render(Provider({ store: setupStore() }, + ConsoleApiCall({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()).toBe(message.parameters[0]); expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/); message = stubPreparedMessages.get("console.timeLog('bar') - 2"); - wrapper = render(ConsoleApiCall({ message, serviceContainer })); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + wrapper = render(Provider({ store: setupStore() }, + ConsoleApiCall({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()) .toMatch(/^bar: \d+(\.\d+)?ms second call Object \{ state\: 1 \}$/); }); @@ -519,7 +529,10 @@ describe("ConsoleAPICall component:", () => { describe("console.dirxml", () => { it("renders", () => { const message = stubPreparedMessages.get("console.dirxml(window)"); - const wrapper = render(ConsoleApiCall({ message, serviceContainer })); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, + ConsoleApiCall({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()) .toBe("Window http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html"); @@ -529,7 +542,11 @@ describe("ConsoleAPICall component:", () => { describe("console.dir", () => { it("renders", () => { const message = stubPreparedMessages.get("console.dir({C, M, Y, K})"); - const wrapper = render(ConsoleApiCall({ message, serviceContainer })); + + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, + ConsoleApiCall({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()) .toBe(`Object { cyan: "C", magenta: "M", yellow: "Y", black: "K" }`); diff --git a/devtools/client/webconsole/test/components/console-output.test.js b/devtools/client/webconsole/test/components/console-output.test.js index 7e0ded838587..d24aee56f57b 100644 --- a/devtools/client/webconsole/test/components/console-output.test.js +++ b/devtools/client/webconsole/test/components/console-output.test.js @@ -8,6 +8,7 @@ const { // Test utils. const expect = require("expect"); const { render } = require("enzyme"); +const Provider = createFactory(require("react-redux").Provider); const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput")); const serviceContainer = require("devtools/client/webconsole/test/fixtures/serviceContainer"); @@ -37,13 +38,19 @@ function getDefaultProps(initialized) { describe("ConsoleOutput component:", () => { it("Render only the last messages that fits the viewport when non-initialized", () => { - const rendered = render(ConsoleOutput(getDefaultProps(false))); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const rendered = render(Provider({ store: setupStore() }, + ConsoleOutput(getDefaultProps(false)))); const messagesNumber = rendered.find(".message").length; expect(messagesNumber).toBe(getInitialMessageCountForViewport(window)); }); it("Render every message when initialized", () => { - const rendered = render(ConsoleOutput(getDefaultProps(true))); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const rendered = render(Provider({ store: setupStore() }, + ConsoleOutput(getDefaultProps(true)))); expect(rendered.find(".message").length).toBe(MESSAGES_NUMBER); }); }); diff --git a/devtools/client/webconsole/test/components/evaluation-result.test.js b/devtools/client/webconsole/test/components/evaluation-result.test.js index 2c0d9d111ee7..3f702947a755 100644 --- a/devtools/client/webconsole/test/components/evaluation-result.test.js +++ b/devtools/client/webconsole/test/components/evaluation-result.test.js @@ -23,7 +23,10 @@ const serviceContainer = require("devtools/client/webconsole/test/fixtures/servi describe("EvaluationResult component:", () => { it("renders a grip result", () => { const message = stubPreparedMessages.get("new Date(0)"); - const wrapper = render(EvaluationResult({ message, serviceContainer })); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, + EvaluationResult({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z"); @@ -70,7 +73,10 @@ describe("EvaluationResult component:", () => { it("renders an inspect command result", () => { const message = stubPreparedMessages.get("inspect({a: 1})"); - const wrapper = render(EvaluationResult({ message, serviceContainer })); + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, + EvaluationResult({ message, serviceContainer }))); expect(wrapper.find(".message-body").text()).toBe("Object { a: 1 }"); }); @@ -108,15 +114,18 @@ describe("EvaluationResult component:", () => { const message = stubPreparedMessages.get("new Date(0)"); const indent = 10; - let wrapper = render(EvaluationResult({ + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + let wrapper = render(Provider({ store: setupStore() }, EvaluationResult({ message: Object.assign({}, message, {indent}), serviceContainer, - })); + }))); let indentEl = wrapper.find(".indent"); expect(indentEl.prop("style").width).toBe(`${indent * INDENT_WIDTH}px`); expect(indentEl.prop("data-indent")).toBe(`${indent}`); - wrapper = render(EvaluationResult({ message, serviceContainer})); + wrapper = render(Provider({ store: setupStore() }, + EvaluationResult({ message, serviceContainer}))); indentEl = wrapper.find(".indent"); expect(indentEl.prop("style").width).toBe(`0`); expect(indentEl.prop("data-indent")).toBe(`0`); @@ -133,11 +142,13 @@ describe("EvaluationResult component:", () => { it("has a timestamp when passed a truthy timestampsVisible prop", () => { const message = stubPreparedMessages.get("new Date(0)"); - const wrapper = render(EvaluationResult({ + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, EvaluationResult({ message, serviceContainer, timestampsVisible: true, - })); + }))); const { timestampString } = require("devtools/client/webconsole/webconsole-l10n"); expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp)); @@ -145,11 +156,13 @@ describe("EvaluationResult component:", () => { it("does not have a timestamp when timestampsVisible prop is falsy", () => { const message = stubPreparedMessages.get("new Date(0)"); - const wrapper = render(EvaluationResult({ + // We need to wrap the ConsoleApiElement in a Provider in order for the + // ObjectInspector to work. + const wrapper = render(Provider({ store: setupStore() }, EvaluationResult({ message, serviceContainer, timestampsVisible: false, - })); + }))); expect(wrapper.find(".timestamp").length).toBe(0); }); diff --git a/devtools/client/webconsole/test/components/new-console-output-wrapper.test.js b/devtools/client/webconsole/test/components/new-console-output-wrapper.test.js index 935abb95c782..5371c962bda8 100644 --- a/devtools/client/webconsole/test/components/new-console-output-wrapper.test.js +++ b/devtools/client/webconsole/test/components/new-console-output-wrapper.test.js @@ -20,7 +20,7 @@ const WebConsoleOutputWrapper = const { messagesAdd } = require("devtools/client/webconsole/actions/messages"); -function getWebConsoleOutputWrapper() { +async function getWebConsoleOutputWrapper() { const hud = { proxy: { releaseActor: () => {}, @@ -31,12 +31,16 @@ function getWebConsoleOutputWrapper() { }, }, }; - return new WebConsoleOutputWrapper(null, hud); + + const owner = { target: {client: {} } }; + const wcow = new WebConsoleOutputWrapper(null, hud, null, owner); + await wcow.init(); + return wcow; } describe("WebConsoleOutputWrapper", () => { - it("clears queues when dispatchMessagesClear is called", () => { - const ncow = getWebConsoleOutputWrapper(); + it("clears queues when dispatchMessagesClear is called", async () => { + const ncow = await getWebConsoleOutputWrapper(); ncow.queuedMessageAdds.push({fakePacket: "message"}); ncow.queuedMessageUpdates.push({fakePacket: "message-update"}); ncow.queuedRequestUpdates.push({fakePacket: "request-update"}); @@ -48,24 +52,30 @@ describe("WebConsoleOutputWrapper", () => { expect(ncow.queuedRequestUpdates.length).toBe(0); }); - it("removes private packets from message queue on dispatchPrivateMessagesClear", () => { - const ncow = getWebConsoleOutputWrapper(); + it("removes private packets from message queue on dispatchPrivateMessagesClear", + async () => { + const ncow = await getWebConsoleOutputWrapper(); - const publicLog = stubPackets.get("console.log('mymap')"); - ncow.queuedMessageAdds.push( - getPrivatePacket("console.trace()"), - publicLog, - getPrivatePacket("XHR POST request"), - ); + const publicLog = stubPackets.get("console.log('mymap')"); + ncow.queuedMessageAdds.push( + getPrivatePacket("console.trace()"), + publicLog, + getPrivatePacket("XHR POST request"), + ); - ncow.dispatchPrivateMessagesClear(); - - expect(ncow.queuedMessageAdds).toEqual([publicLog]); - }); + ncow.dispatchPrivateMessagesClear(); + expect(ncow.queuedMessageAdds).toEqual([publicLog]); + }); it("removes private packets from network update queue on dispatchPrivateMessagesClear", - () => { - const ncow = getWebConsoleOutputWrapper(); + async () => { + const ncow = await getWebConsoleOutputWrapper(); + const publicLog = stubPackets.get("console.log('mymap')"); + ncow.queuedMessageAdds.push( + getPrivatePacket("console.trace()"), + publicLog, + getPrivatePacket("XHR POST request"), + ); const postId = Symbol(); const getId = Symbol(); @@ -97,8 +107,8 @@ describe("WebConsoleOutputWrapper", () => { }); it("removes private packets from network request queue on dispatchPrivateMessagesClear", - () => { - const ncow = getWebConsoleOutputWrapper(); + async () => { + const ncow = await getWebConsoleOutputWrapper(); ncow.getStore().dispatch(messagesAdd([ stubPackets.get("GET request"), diff --git a/devtools/client/webconsole/utils/object-inspector.js b/devtools/client/webconsole/utils/object-inspector.js index 7b83f400fe09..d206c0d5857c 100644 --- a/devtools/client/webconsole/utils/object-inspector.js +++ b/devtools/client/webconsole/utils/object-inspector.js @@ -5,12 +5,10 @@ "use strict"; const { createFactory } = require("devtools/client/shared/vendor/react"); -const ObjectClient = require("devtools/shared/client/object-client"); -const LongStringClient = require("devtools/shared/client/long-string-client"); const reps = require("devtools/client/shared/components/reps/reps"); -const { REPS, MODE } = reps; -const ObjectInspector = createFactory(reps.ObjectInspector); +const { REPS, MODE, objectInspector } = reps; +const ObjectInspector = createFactory(objectInspector.ObjectInspector); const { Grip } = REPS; /** @@ -30,6 +28,7 @@ function getObjectInspector(grip, serviceContainer, override = {}) { let onDOMNodeMouseOver; let onDOMNodeMouseOut; let onInspectIconClick; + if (serviceContainer) { onDOMNodeMouseOver = serviceContainer.highlightDomElement ? (object) => serviceContainer.highlightDomElement(object) @@ -50,16 +49,6 @@ function getObjectInspector(grip, serviceContainer, override = {}) { autoExpandDepth: 0, mode: MODE.LONG, roots, - createObjectClient: object => - new ObjectClient(serviceContainer.hudProxy.client, object), - createLongStringClient: object => - new LongStringClient(serviceContainer.hudProxy.client, object), - releaseActor: actor => { - if (!actor || !serviceContainer.hudProxy.releaseActor) { - return; - } - serviceContainer.hudProxy.releaseActor(actor); - }, onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger, recordTelemetryEvent: serviceContainer.recordTelemetryEvent, openLink: serviceContainer.openLink, @@ -85,7 +74,7 @@ function getObjectInspector(grip, serviceContainer, override = {}) { function createRootsFromGrip(grip) { return [{ - path: (grip && grip.actor) || JSON.stringify(grip), + path: Symbol((grip && grip.actor) || JSON.stringify(grip)), contents: { value: grip } }]; } diff --git a/devtools/client/webconsole/webconsole-frame.js b/devtools/client/webconsole/webconsole-frame.js index dc2415a6a8a4..fd68d2c6bff9 100644 --- a/devtools/client/webconsole/webconsole-frame.js +++ b/devtools/client/webconsole/webconsole-frame.js @@ -80,6 +80,10 @@ WebConsoleFrame.prototype = { await this._initConnection(); await this.consoleOutput.init(); + // Toggle the timestamp on preference change + Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged); + this._onToolboxPrefChanged(); + const id = WebConsoleUtils.supportsString(this.hudId); if (Services.obs) { Services.obs.notifyObservers(id, "web-console-created"); @@ -233,10 +237,8 @@ WebConsoleFrame.prototype = { const toolbox = gDevTools.getToolbox(this.owner.target); this.consoleOutput = new this.window.WebConsoleOutput( - this.outputNode, this, toolbox, this.owner, this.document); - // Toggle the timestamp on preference change - Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged); - this._onToolboxPrefChanged(); + this.outputNode, this, toolbox, this.owner, this.document + ); this._initShortcuts(); this._initOutputSyntaxHighlighting(); diff --git a/devtools/client/webconsole/webconsole-output-wrapper.js b/devtools/client/webconsole/webconsole-output-wrapper.js index 5e4b06daddbe..c30b9ed316fa 100644 --- a/devtools/client/webconsole/webconsole-output-wrapper.js +++ b/devtools/client/webconsole/webconsole-output-wrapper.js @@ -12,12 +12,15 @@ const { Provider } = require("devtools/client/shared/vendor/react-redux"); const actions = require("devtools/client/webconsole/actions/index"); const { createContextMenu, createEditContextMenu } = require("devtools/client/webconsole/utils/context-menu"); const { configureStore } = require("devtools/client/webconsole/store"); + const { isPacketPrivate } = require("devtools/client/webconsole/utils/messages"); const { getAllMessagesById, getMessage } = require("devtools/client/webconsole/selectors/messages"); const Telemetry = require("devtools/client/shared/telemetry"); const EventEmitter = require("devtools/shared/event-emitter"); const App = createFactory(require("devtools/client/webconsole/components/App")); +const ObjectClient = require("devtools/shared/client/object-client"); +const LongStringClient = require("devtools/shared/client/long-string-client"); let store = null; @@ -38,12 +41,6 @@ function WebConsoleOutputWrapper(parentNode, hud, toolbox, owner, document) { this.throttledDispatchPromise = null; this.telemetry = new Telemetry(); - - store = configureStore(this.hud, { - // We may not have access to the toolbox (e.g. in the browser console). - sessionId: this.toolbox && this.toolbox.sessionId || -1, - telemetry: this.telemetry, - }); } WebConsoleOutputWrapper.prototype = { @@ -53,6 +50,8 @@ WebConsoleOutputWrapper.prototype = { this.hud[id] = node; }; const { hud } = this; + const debuggerClient = this.owner.target.client; + const serviceContainer = { attachRefToHud, emitNewMessage: (node, messageId, timeStamp) => { @@ -85,6 +84,21 @@ WebConsoleOutputWrapper.prototype = { ...extra, "session_id": this.toolbox && this.toolbox.sessionId || -1 }); + }, + createObjectClient: (object) => { + return new ObjectClient(debuggerClient, object); + }, + + createLongStringClient: (object) => { + return new LongStringClient(debuggerClient, object); + }, + + releaseActor: (actor) => { + if (!actor) { + return null; + } + + return debuggerClient.release(actor); } }; @@ -210,6 +224,13 @@ WebConsoleOutputWrapper.prototype = { }); } + store = configureStore(this.hud, { + // We may not have access to the toolbox (e.g. in the browser console). + sessionId: this.toolbox && this.toolbox.sessionId || -1, + telemetry: this.telemetry, + services: serviceContainer + }); + const {prefs} = store.getState(); const app = App({ attachRefToHud, @@ -223,8 +244,13 @@ WebConsoleOutputWrapper.prototype = { }); // Render the root Application component. - const provider = createElement(Provider, { store }, app); - this.body = ReactDOM.render(provider, this.parentNode); + if (this.parentNode) { + const provider = createElement(Provider, { store }, app); + this.body = ReactDOM.render(provider, this.parentNode); + } else { + // If there's no parentNode, we are in a test. So we can resolve immediately. + resolve(); + } }); }, From 273a59bcda7ceec07c60c39b80f5fea55aea419e Mon Sep 17 00:00:00 2001 From: Bogdan Tara Date: Thu, 27 Sep 2018 23:38:04 +0300 Subject: [PATCH 10/25] Backed out changeset e7624782898d (bug 1487428) for test_inspector-insert.html failures CLOSED TREE --- .../test/browser_css_autocompletion.js | 3 +- .../browser_editor_autocomplete_events.js | 3 +- ...owser_accessibility_highlighter_infobar.js | 4 +- .../browser/browser_accessibility_node.js | 4 +- .../browser_accessibility_node_events.js | 4 +- .../browser/browser_accessibility_simple.js | 4 +- .../browser/browser_accessibility_walker.js | 4 +- .../browser_animation_emitMutations.js | 4 +- .../browser_animation_getMultipleStates.js | 4 +- .../browser/browser_animation_getPlayers.js | 4 +- .../browser_animation_getProperties.js | 4 +- ...browser_animation_getStateAfterFinished.js | 4 +- .../browser_animation_getSubTreeAnimations.js | 4 +- .../browser/browser_animation_keepFinished.js | 4 +- .../browser_animation_playPauseIframe.js | 4 +- .../browser_animation_playPauseSeveral.js | 4 +- .../browser/browser_animation_playerState.js | 4 +- .../browser_animation_reconstructState.js | 4 +- .../browser_animation_refreshTransitions.js | 4 +- .../browser_animation_setCurrentTime.js | 4 +- .../browser_animation_setPlaybackRate.js | 4 +- .../tests/browser/browser_animation_simple.js | 4 +- .../browser/browser_animation_updatedState.js | 4 +- .../tests/browser/browser_layout_getGrids.js | 4 +- .../tests/browser/browser_layout_simple.js | 4 +- devtools/server/tests/browser/head.js | 63 ++++++++++--------- .../mochitest/test_inspector-insert.html | 8 +-- 27 files changed, 85 insertions(+), 84 deletions(-) diff --git a/devtools/client/sourceeditor/test/browser_css_autocompletion.js b/devtools/client/sourceeditor/test/browser_css_autocompletion.js index dc71f2aebd62..7db42cfdc568 100644 --- a/devtools/client/sourceeditor/test/browser_css_autocompletion.js +++ b/devtools/client/sourceeditor/test/browser_css_autocompletion.js @@ -5,6 +5,7 @@ "use strict"; const CSSCompleter = require("devtools/client/sourceeditor/css-autocompleter"); +const {InspectorFront} = require("devtools/shared/fronts/inspector"); const CSS_URI = "http://mochi.test:8888/browser/devtools/client/sourceeditor" + "/test/css_statemachine_testcases.css"; @@ -85,7 +86,7 @@ add_task(async function test() { async function runTests() { const target = await TargetFactory.forTab(gBrowser.selectedTab); await target.attach(); - inspector = target.getFront("inspector"); + inspector = InspectorFront(target.client, target.form); const walker = await inspector.getWalker(); completer = new CSSCompleter({walker: walker, cssProperties: getClientCssProperties()}); diff --git a/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js b/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js index 970f0d485f98..1f6f6d230f44 100644 --- a/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js +++ b/devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js @@ -4,6 +4,7 @@ "use strict"; +const {InspectorFront} = require("devtools/shared/fronts/inspector"); const TEST_URI = "data:text/html;charset=UTF-8," + "
"; @@ -15,7 +16,7 @@ add_task(async function() { async function runTests() { const target = await TargetFactory.forTab(gBrowser.selectedTab); await target.attach(); - const inspector = target.getFront("inspector"); + const inspector = InspectorFront(target.client, target.form); const walker = await inspector.getWalker(); const {ed, win, edWin} = await setup(null, { autocomplete: true, diff --git a/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js index e6e4a13cc86e..728d2f36a3d4 100644 --- a/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js +++ b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js @@ -10,7 +10,7 @@ const { truncateString } = require("devtools/shared/inspector/utils"); const { MAX_STRING_LENGTH } = require("devtools/server/actors/highlighters/utils/accessibility"); add_task(async function() { - const { target, walker, accessibility } = + const { client, walker, accessibility } = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility_infobar.html"); const a11yWalker = await accessibility.getWalker(); @@ -25,7 +25,7 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_node.js b/devtools/server/tests/browser/browser_accessibility_node.js index 4cc515e0c7f0..00a260b1c00d 100644 --- a/devtools/server/tests/browser/browser_accessibility_node.js +++ b/devtools/server/tests/browser/browser_accessibility_node.js @@ -7,7 +7,7 @@ // Checks for the AccessibleActor add_task(async function() { - const {target, walker, accessibility} = + const {client, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const modifiers = Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; @@ -50,6 +50,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_node_events.js b/devtools/server/tests/browser/browser_accessibility_node_events.js index 8fd087618c60..8ba83601a934 100644 --- a/devtools/server/tests/browser/browser_accessibility_node_events.js +++ b/devtools/server/tests/browser/browser_accessibility_node_events.js @@ -7,7 +7,7 @@ // Checks for the AccessibleActor events add_task(async function() { - const {target, walker, accessibility} = + const {client, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const modifiers = Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; @@ -123,6 +123,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_simple.js b/devtools/server/tests/browser/browser_accessibility_simple.js index 269ccab16bf2..98bbc8382ac4 100644 --- a/devtools/server/tests/browser/browser_accessibility_simple.js +++ b/devtools/server/tests/browser/browser_accessibility_simple.js @@ -16,7 +16,7 @@ function checkAccessibilityState(accessibility, expected) { // Simple checks for the AccessibilityActor and AccessibleWalkerActor add_task(async function() { - const { walker: domWalker, target, accessibility} = await initAccessibilityFrontForUrl( + const { walker: domWalker, client, accessibility} = await initAccessibilityFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(accessibility, "The AccessibilityFront was created"); @@ -63,6 +63,6 @@ add_task(async function() { checkAccessibilityState(accessibility, { enabled: false, canBeDisabled: true, canBeEnabled: true }); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_accessibility_walker.js b/devtools/server/tests/browser/browser_accessibility_walker.js index e4f34b2c3810..2258de35e4e1 100644 --- a/devtools/server/tests/browser/browser_accessibility_walker.js +++ b/devtools/server/tests/browser/browser_accessibility_walker.js @@ -7,7 +7,7 @@ // Checks for the AccessibleWalkerActor add_task(async function() { - const {target, walker, accessibility} = + const {client, walker, accessibility} = await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html"); const a11yWalker = await accessibility.getWalker(); @@ -127,6 +127,6 @@ add_task(async function() { await accessibility.disable(); await waitForA11yShutdown(); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_emitMutations.js b/devtools/server/tests/browser/browser_animation_emitMutations.js index 170e1c895599..20c3d8b3f5a3 100644 --- a/devtools/server/tests/browser/browser_animation_emitMutations.js +++ b/devtools/server/tests/browser/browser_animation_emitMutations.js @@ -8,7 +8,7 @@ // node after getAnimationPlayersForNode was called on that node. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non-animated node"); @@ -57,6 +57,6 @@ add_task(async function() { ok(changes[1].player === p1 || changes[1].player === p2, "The second removed player was one of the previously added players"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getMultipleStates.js b/devtools/server/tests/browser/browser_animation_getMultipleStates.js index eaf9ce43a05f..93c22de47f47 100644 --- a/devtools/server/tests/browser/browser_animation_getMultipleStates.js +++ b/devtools/server/tests/browser/browser_animation_getMultipleStates.js @@ -8,12 +8,12 @@ // multiple animations. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasAnInitialState(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getPlayers.js b/devtools/server/tests/browser/browser_animation_getPlayers.js index a23659f167b5..f3389c9d715d 100644 --- a/devtools/server/tests/browser/browser_animation_getPlayers.js +++ b/devtools/server/tests/browser/browser_animation_getPlayers.js @@ -7,12 +7,12 @@ // Check the output of getAnimationPlayersForNode add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await theRightNumberOfPlayersIsReturned(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getProperties.js b/devtools/server/tests/browser/browser_animation_getProperties.js index 6043ca787def..8ac0561c6406 100644 --- a/devtools/server/tests/browser/browser_animation_getProperties.js +++ b/devtools/server/tests/browser/browser_animation_getProperties.js @@ -10,7 +10,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { - const {target, walker, animations} = await initAnimationsFrontForUrl(URL); + const {client, walker, animations} = await initAnimationsFrontForUrl(URL); info("Get the test node and its animation front"); const node = await walker.querySelector(walker.rootNode, ".simple-animation"); @@ -31,6 +31,6 @@ add_task(async function() { // purpose. This object comes straight out of the web animations API // unmodified. - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js index fe4eccdd06b6..8c79dab8345a 100644 --- a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js +++ b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js @@ -12,7 +12,7 @@ // information. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non animated node"); @@ -51,6 +51,6 @@ add_task(async function() { is(players[1].state.iterationCount, 100, "The iterationCount of the second animation is correct"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js index 04b4110bbf3b..9760cef4934b 100644 --- a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js +++ b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js @@ -12,7 +12,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { info("Creating a test document with 2 iframes containing animated nodes"); - const {target, walker, animations} = await initAnimationsFrontForUrl( + const {client, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8," + ""); @@ -33,6 +33,6 @@ add_task(async function() { // at least have the infinitely running animations. ok(players.length >= 4, "All subtree animations were retrieved"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_keepFinished.js b/devtools/server/tests/browser/browser_animation_keepFinished.js index 825bb18e0700..af19dbd39cf0 100644 --- a/devtools/server/tests/browser/browser_animation_keepFinished.js +++ b/devtools/server/tests/browser/browser_animation_keepFinished.js @@ -11,7 +11,7 @@ // AnimationPlayerActor. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve a non-animated node"); @@ -44,7 +44,7 @@ add_task(async function() { animations.off("mutations", onMutations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playPauseIframe.js b/devtools/server/tests/browser/browser_animation_playPauseIframe.js index 03af6327db66..621f53c4bacb 100644 --- a/devtools/server/tests/browser/browser_animation_playPauseIframe.js +++ b/devtools/server/tests/browser/browser_animation_playPauseIframe.js @@ -12,7 +12,7 @@ const URL = MAIN_DOMAIN + "animation.html"; add_task(async function() { info("Creating a test document with 2 iframes containing animated nodes"); - const {target, walker, animations} = await initAnimationsFrontForUrl( + const {client, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8," + "" + ""); @@ -29,7 +29,7 @@ add_task(async function() { await toggleAndCheckStates(animations, nodeInFrame1, "running"); await toggleAndCheckStates(animations, nodeInFrame2, "running"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js index 839b5511240a..773f40807fab 100644 --- a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js +++ b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js @@ -15,7 +15,7 @@ const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations", ".delayed-animation"]; add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Pause all animations in the test document"); @@ -24,7 +24,7 @@ add_task(async function() { info("Play all animations in the test document"); await toggleAndCheckStates(walker, animations, ALL_ANIMATED_NODES, "running"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_playerState.js b/devtools/server/tests/browser/browser_animation_playerState.js index f8e802011397..94b9a41a8a0c 100644 --- a/devtools/server/tests/browser/browser_animation_playerState.js +++ b/devtools/server/tests/browser/browser_animation_playerState.js @@ -7,13 +7,13 @@ // Check the animation player's initial state add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasAnInitialState(walker, animations); await playerStateIsCorrect(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_reconstructState.js b/devtools/server/tests/browser/browser_animation_reconstructState.js index 18de3bddb544..617cb49f4c5c 100644 --- a/devtools/server/tests/browser/browser_animation_reconstructState.js +++ b/devtools/server/tests/browser/browser_animation_reconstructState.js @@ -8,12 +8,12 @@ // state that change, the front reconstructs the whole state everytime. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playerHasCompleteStateAtAllTimes(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_refreshTransitions.js b/devtools/server/tests/browser/browser_animation_refreshTransitions.js index 0bacb6766a01..b72252e9838a 100644 --- a/devtools/server/tests/browser/browser_animation_refreshTransitions.js +++ b/devtools/server/tests/browser/browser_animation_refreshTransitions.js @@ -9,7 +9,7 @@ // AnimationPlayerFront should be sent, and the old one should be removed. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve the test node"); @@ -49,7 +49,7 @@ add_task(async function() { is(reportedMutations.filter(m => m.type === "added").length, 2, "2 'added' events were sent (for the new transitions)"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_setCurrentTime.js b/devtools/server/tests/browser/browser_animation_setCurrentTime.js index 352dbff882ca..bd3240e16ae5 100644 --- a/devtools/server/tests/browser/browser_animation_setCurrentTime.js +++ b/devtools/server/tests/browser/browser_animation_setCurrentTime.js @@ -7,12 +7,12 @@ // Check that the AnimationsActor allows changing many players' currentTimes at once. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await testSetCurrentTimes(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js index 9b4aa374d05f..4fcf0027f4a9 100644 --- a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js +++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js @@ -8,7 +8,7 @@ // can have their rates changed at the same time. add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); info("Retrieve an animated node"); @@ -44,6 +44,6 @@ add_task(async function() { is(animPlayerState.playbackRate, .5, "The playbackRate was updated"); } - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_simple.js b/devtools/server/tests/browser/browser_animation_simple.js index 9cbd172ffae3..99039e888405 100644 --- a/devtools/server/tests/browser/browser_animation_simple.js +++ b/devtools/server/tests/browser/browser_animation_simple.js @@ -7,7 +7,7 @@ // Simple checks for the AnimationsActor add_task(async function() { - const {target, walker, animations} = await initAnimationsFrontForUrl( + const {client, walker, animations} = await initAnimationsFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(animations, "The AnimationsFront was created"); @@ -32,6 +32,6 @@ add_task(async function() { ok(Array.isArray(players), "An array of players was returned"); is(players.length, 0, "0 players have been returned for the invalid node"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_animation_updatedState.js b/devtools/server/tests/browser/browser_animation_updatedState.js index 5839ce178eec..dcb5f7d40a80 100644 --- a/devtools/server/tests/browser/browser_animation_updatedState.js +++ b/devtools/server/tests/browser/browser_animation_updatedState.js @@ -8,12 +8,12 @@ // Check the animation player's updated state add_task(async function() { - const {target, walker, animations} = + const {client, walker, animations} = await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); await playStateIsUpdatedDynamically(walker, animations); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_layout_getGrids.js b/devtools/server/tests/browser/browser_layout_getGrids.js index e2e33b4bbe94..a3fad397b958 100644 --- a/devtools/server/tests/browser/browser_layout_getGrids.js +++ b/devtools/server/tests/browser/browser_layout_getGrids.js @@ -108,7 +108,7 @@ const GRID_FRAGMENT_DATA = { }; add_task(async function() { - const { target, walker, layout } = + const { client, walker, layout } = await initLayoutFrontForUrl(MAIN_DOMAIN + "grid.html"); const grids = await layout.getGrids(walker.rootNode); const grid = grids[0]; @@ -129,6 +129,6 @@ add_task(async function() { ok(false, "Did not get grid container node front."); } - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/browser_layout_simple.js b/devtools/server/tests/browser/browser_layout_simple.js index bbf1b55aa42c..0f24bebe1bec 100644 --- a/devtools/server/tests/browser/browser_layout_simple.js +++ b/devtools/server/tests/browser/browser_layout_simple.js @@ -7,7 +7,7 @@ // Simple checks for the LayoutActor and GridActor add_task(async function() { - const {target, walker, layout} = await initLayoutFrontForUrl( + const {client, walker, layout} = await initLayoutFrontForUrl( "data:text/html;charset=utf-8,test
"); ok(layout, "The LayoutFront was created"); @@ -26,6 +26,6 @@ add_task(async function() { ok(Array.isArray(grids), "An array of grids was returned"); is(grids.length, 0, "0 grids have been returned for the invalid node"); - await target.destroy(); + await client.close(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js index 353d53436690..5495cc8db84b 100644 --- a/devtools/server/tests/browser/head.js +++ b/devtools/server/tests/browser/head.js @@ -41,50 +41,43 @@ var addTab = async function(url) { const tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - info(`Tab added a URL ${url} loaded`); + info(`Tab added and URL ${url} loaded`); return tab.linkedBrowser; }; -// does almost the same thing as addTab, but directly returns an object -async function addTabTarget(url) { - info(`Adding a new tab with URL: ${url}`); - const tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url); - await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - info(`Tab added a URL ${url} loaded`); - return getTargetForTab(tab); -} - -async function getTargetForTab(tab) { - const target = await TargetFactory.forTab(tab); - info("Attaching to the active tab."); - await target.attach(); - return target; -} - async function initAnimationsFrontForUrl(url) { - const { inspector, walker, target } = await initInspectorFront(url); - const animations = target.getFront("animations"); + const {AnimationsFront} = require("devtools/shared/fronts/animation"); - return {inspector, walker, animations, target}; + const { inspector, walker, client, form } = await initInspectorFront(url); + const animations = AnimationsFront(client, form); + + return {inspector, walker, animations, client}; } async function initLayoutFrontForUrl(url) { - const {inspector, walker, target} = await initInspectorFront(url); + const {inspector, walker, client} = await initInspectorFront(url); const layout = await walker.getLayoutInspector(); - return {inspector, walker, layout, target}; + return {inspector, walker, layout, client}; } async function initAccessibilityFrontForUrl(url) { - const target = await addTabTarget(url); - const inspector = target.getFront("inspector"); + const {AccessibilityFront} = require("devtools/shared/fronts/accessibility"); + const {InspectorFront} = require("devtools/shared/fronts/inspector"); + + await addTab(url); + + initDebuggerServer(); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + const form = await connectDebuggerClient(client); + const inspector = InspectorFront(client, form); const walker = await inspector.getWalker(); - const accessibility = target.getFront("accessibility"); + const accessibility = AccessibilityFront(client, form); await accessibility.bootstrap(); - return {inspector, walker, accessibility, target}; + return {inspector, walker, accessibility, client}; } function initDebuggerServer() { @@ -100,20 +93,28 @@ function initDebuggerServer() { } async function initPerfFront() { + const {PerfFront} = require("devtools/shared/fronts/perf"); + initDebuggerServer(); const client = new DebuggerClient(DebuggerServer.connectPipe()); await waitUntilClientConnected(client); - const front = await client.mainRoot.getFront("perf"); + const rootForm = await getRootForm(client); + const front = PerfFront(client, rootForm); return {front, client}; } async function initInspectorFront(url) { - const target = await addTabTarget(url); + const { InspectorFront } = require("devtools/shared/fronts/inspector"); + await addTab(url); + + initDebuggerServer(); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + const form = await connectDebuggerClient(client); + const inspector = InspectorFront(client, form); - const inspector = target.getFront("inspector"); const walker = await inspector.getWalker(); - return {inspector, walker, target}; + return {inspector, walker, client, form}; } /** @@ -248,7 +249,7 @@ function waitForMarkerType(front, types, predicate, } const markers = unpackFun(name, data); - info("Got markers"); + info("Got markers: " + JSON.stringify(markers, null, 2)); filteredMarkers = filteredMarkers.concat( markers.filter(m => types.includes(m.name))); diff --git a/devtools/server/tests/mochitest/test_inspector-insert.html b/devtools/server/tests/mochitest/test_inspector-insert.html index 505713b70d79..69a63c0fb852 100644 --- a/devtools/server/tests/mochitest/test_inspector-insert.html +++ b/devtools/server/tests/mochitest/test_inspector-insert.html @@ -25,12 +25,10 @@ let gInspectee = null; addTest(function setup() { const url = document.getElementById("inspectorContent").href; - attachURL(url, async function(err, client, tab, doc) { + attachURL(url, function(err, client, tab, doc) { gInspectee = doc; - const {TargetFactory} = require("devtools/client/framework/target"); - const target = await TargetFactory.forTab(tab); - await target.attach(); - const inspectorFront = target.getFront("inspector"); + const {InspectorFront} = require("devtools/shared/fronts/inspector"); + const inspectorFront = InspectorFront(client, tab); promiseDone(inspectorFront.getWalker().then(walker => { ok(walker, "getWalker() should return an actor."); gWalker = walker; From 8451acf329ff2abf3074edd6e2866044abf44071 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 27 Sep 2018 20:28:31 +0000 Subject: [PATCH 11/25] Bug 1494513: Work around VRThread shutdown bug causing frequent intermittent failures. r=erahm Differential Revision: https://phabricator.services.mozilla.com/D7134 --HG-- extra : moz-landing-system : lando --- gfx/vr/VRThread.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/gfx/vr/VRThread.cpp b/gfx/vr/VRThread.cpp index 3a950feaa405..fe5d01737951 100644 --- a/gfx/vr/VRThread.cpp +++ b/gfx/vr/VRThread.cpp @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "VRThread.h" +#include "nsDebug.h" +#include "nsThreadManager.h" #include "nsThreadUtils.h" #include "VRManager.h" @@ -161,7 +163,16 @@ void VRThread::Shutdown() { if (mThread) { - mThread->Shutdown(); + if (nsThreadManager::get().IsNSThread()) { + mThread->Shutdown(); + } else { + NS_WARNING("VRThread::Shutdown() may only be called from an XPCOM thread"); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "VRThread::Shutdown", + [thread = mThread]() { + thread->Shutdown(); + })); + } mThread = nullptr; } mStarted = false; From 4d700e555a914c13c4cc14a3d843a354e386320c Mon Sep 17 00:00:00 2001 From: Gabriele Svelto Date: Thu, 27 Sep 2018 20:41:39 +0000 Subject: [PATCH 12/25] Bug 1493955 - Store floating-point preferences in a locale-independent way r=njn Differential Revision: https://phabricator.services.mozilla.com/D6796 --HG-- extra : moz-landing-system : lando --- modules/libpref/Preferences.cpp | 9 +++++--- modules/libpref/Preferences.h | 5 +++-- modules/libpref/test/gtest/Basics.cpp | 21 +++++++++++++++++++ .../antitracking/AntiTrackingCommon.cpp | 1 + .../extensions/ExtensionPolicyService.cpp | 1 + .../tests/gtest/TestCombinedStacks.cpp | 2 ++ toolkit/recordreplay/ipc/ChildIPC.cpp | 1 + xpcom/threads/Scheduler.cpp | 1 - xpcom/threads/Scheduler.h | 1 + 9 files changed, 36 insertions(+), 6 deletions(-) diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index c211fba48110..319580ca3ed6 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -2394,6 +2394,7 @@ nsPrefBranch::GetFloatPref(const char* aPrefName, float* aRetVal) nsAutoCString stringVal; nsresult rv = GetCharPref(aPrefName, stringVal); if (NS_SUCCEEDED(rv)) { + // ToFloat() does a locale-independent conversion. *aRetVal = stringVal.ToFloat(&rv); } @@ -5035,6 +5036,7 @@ Preferences::GetFloat(const char* aPrefName, nsAutoCString result; nsresult rv = Preferences::GetCString(aPrefName, result, aKind); if (NS_SUCCEEDED(rv)) { + // ToFloat() does a locale-independent conversion. *aResult = result.ToFloat(&rv); } return rv; @@ -5845,7 +5847,9 @@ static void SetPref_float(const char* aName, float aDefaultValue) { PrefValue value; - nsPrintfCString defaultValue("%f", aDefaultValue); + // Convert the value in a locale-independent way. + nsAutoCString defaultValue; + defaultValue.AppendFloat(aDefaultValue); value.mStringVal = defaultValue.get(); pref_SetPref(aName, PrefType::String, @@ -5973,8 +5977,7 @@ InitVarCachePref(const nsACString& aName, } } -// XXX: this will eventually become used -MOZ_MAYBE_UNUSED static void +static void InitVarCachePref(const nsACString& aName, float* aCache, float aDefaultValue, diff --git a/modules/libpref/Preferences.h b/modules/libpref/Preferences.h index 1e31b86c5363..a11e9ee46a74 100644 --- a/modules/libpref/Preferences.h +++ b/modules/libpref/Preferences.h @@ -19,7 +19,6 @@ #include "nsIObserver.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" -#include "nsPrintfCString.h" #include "nsString.h" #include "nsTArray.h" #include "nsWeakReference.h" @@ -309,7 +308,9 @@ public: float aValue, PrefValueKind aKind = PrefValueKind::User) { - return SetCString(aPrefName, nsPrintfCString("%f", aValue), aKind); + nsAutoCString value; + value.AppendFloat(aValue); + return SetCString(aPrefName, value, aKind); } static nsresult SetCString(const char* aPrefName, diff --git a/modules/libpref/test/gtest/Basics.cpp b/modules/libpref/test/gtest/Basics.cpp index 28291a619b66..bb14b0ff6843 100644 --- a/modules/libpref/test/gtest/Basics.cpp +++ b/modules/libpref/test/gtest/Basics.cpp @@ -4,6 +4,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + #include "gtest/gtest.h" #include "mozilla/Preferences.h" @@ -34,3 +36,22 @@ TEST(PrefsBasics, Errors) ASSERT_FLOAT_EQ(Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::User), 4.44f); } + +TEST(PrefsBasics, FloatConversions) +{ + // Set a global locale that uses the comma as the decimal separator. Since + // we can't tell which locales will be available on a machine the tests are + // executed only if the locale was set correctly. + const char* oldLocale = setlocale(LC_NUMERIC, "nl_NL"); + if (oldLocale != nullptr) { + Preferences::SetFloat("foo.float", 3.33f, PrefValueKind::Default); + Preferences::SetFloat("foo.float", 4.44f, PrefValueKind::User); + ASSERT_FLOAT_EQ( + Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::Default), 3.33f); + ASSERT_FLOAT_EQ( + Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::User), 4.44f); + + // Restore the original locale + setlocale(LC_NUMERIC, oldLocale); + } +} diff --git a/toolkit/components/antitracking/AntiTrackingCommon.cpp b/toolkit/components/antitracking/AntiTrackingCommon.cpp index 45d446f12101..0b07c3000258 100644 --- a/toolkit/components/antitracking/AntiTrackingCommon.cpp +++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp @@ -30,6 +30,7 @@ #include "nsIWebProgressListener.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" #include "nsScriptSecurityManager.h" #include "nsSandboxFlags.h" #include "prtime.h" diff --git a/toolkit/components/extensions/ExtensionPolicyService.cpp b/toolkit/components/extensions/ExtensionPolicyService.cpp index 93a406408394..288a3f666917 100644 --- a/toolkit/components/extensions/ExtensionPolicyService.cpp +++ b/toolkit/components/extensions/ExtensionPolicyService.cpp @@ -29,6 +29,7 @@ #include "nsILoadInfo.h" #include "nsIXULRuntime.h" #include "nsNetUtil.h" +#include "nsPrintfCString.h" #include "nsPIDOMWindow.h" #include "nsXULAppAPI.h" #include "nsQueryObject.h" diff --git a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp index fa75ea5ace43..d736b9f5685c 100644 --- a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp +++ b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp @@ -4,6 +4,8 @@ #include "other/CombinedStacks.h" #include "other/ProcessedStack.h" +#include "nsPrintfCString.h" + using namespace mozilla::Telemetry; using namespace TelemetryTestHelpers; diff --git a/toolkit/recordreplay/ipc/ChildIPC.cpp b/toolkit/recordreplay/ipc/ChildIPC.cpp index ce2e6f58fc01..6dcb40258032 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.cpp +++ b/toolkit/recordreplay/ipc/ChildIPC.cpp @@ -23,6 +23,7 @@ #include "InfallibleVector.h" #include "MemorySnapshot.h" +#include "nsPrintfCString.h" #include "ParentInternal.h" #include "ProcessRecordReplay.h" #include "ProcessRedirect.h" diff --git a/xpcom/threads/Scheduler.cpp b/xpcom/threads/Scheduler.cpp index d75d0dd0816d..c80b85516412 100644 --- a/xpcom/threads/Scheduler.cpp +++ b/xpcom/threads/Scheduler.cpp @@ -16,7 +16,6 @@ #include "mozilla/SchedulerGroup.h" #include "nsCycleCollector.h" #include "nsIThread.h" -#include "nsPrintfCString.h" #include "nsThread.h" #include "nsThreadManager.h" #include "PrioritizedEventQueue.h" diff --git a/xpcom/threads/Scheduler.h b/xpcom/threads/Scheduler.h index 241bd61d359e..2c3401a90850 100644 --- a/xpcom/threads/Scheduler.h +++ b/xpcom/threads/Scheduler.h @@ -14,6 +14,7 @@ #include "mozilla/UniquePtr.h" #include "nsTArray.h" #include "nsILabelableRunnable.h" +#include "nsPrintfCString.h" // Windows silliness. winbase.h defines an empty no-argument Yield macro. #undef Yield From b18e8c5e3481aa223d0f9bd781e95345e2e94946 Mon Sep 17 00:00:00 2001 From: Randall Barker Date: Thu, 27 Sep 2018 20:08:43 +0000 Subject: [PATCH 13/25] Bug 1493227 - Add environment variable to prevent crash reporter from handling SIGILL r=gsvelto Add env var MOZ_DISABLE_EXCEPTION_HANDLER_SIGILL so that the crash reporter will not register a handler for SIGILL when the env var is set. This is needed to work around a conflict with the Oculus Mobile runtime that uses the SIGILL signal to trap the back button on the controller. Differential Revision: https://phabricator.services.mozilla.com/D6696 --HG-- extra : moz-landing-system : lando --- .../linux/handler/exception_handler.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc index 5ec3010eef38..611b0385f78b 100644 --- a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc +++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc @@ -96,6 +96,7 @@ #include "linux/minidump_writer/minidump_writer.h" #include "common/linux/eintr_wrapper.h" #include "third_party/lss/linux_syscall_support.h" +#include "prenv.h" #if defined(__ANDROID__) #include "linux/sched.h" @@ -105,6 +106,8 @@ #define PR_SET_PTRACER 0x59616d61 #endif +#define SKIP_SIGILL(sig) if (g_skip_sigill_ && (sig == SIGILL)) continue; + namespace google_breakpad { namespace { @@ -213,6 +216,7 @@ pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER; ExceptionHandler::CrashContext g_crash_context_; FirstChanceHandler g_first_chance_handler_ = nullptr; +bool g_skip_sigill_ = false; } // namespace // Runs before crashing: normal context. @@ -227,6 +231,8 @@ ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor, callback_context_(callback_context), minidump_descriptor_(descriptor), crash_handler_(NULL) { + + g_skip_sigill_ = PR_GetEnv("MOZ_DISABLE_EXCEPTION_HANDLER_SIGILL") ? true : false; if (server_fd >= 0) crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd)); @@ -278,6 +284,7 @@ bool ExceptionHandler::InstallHandlersLocked() { // Fail if unable to store all the old handlers. for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1) return false; } @@ -287,13 +294,16 @@ bool ExceptionHandler::InstallHandlersLocked() { sigemptyset(&sa.sa_mask); // Mask all exception signals when we're handling one of them. - for (int i = 0; i < kNumHandledSignals; ++i) + for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); sigaddset(&sa.sa_mask, kExceptionSignals[i]); + } sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_ONSTACK | SA_SIGINFO; for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) { // At this point it is impractical to back out changes, and so failure to // install a signal is intentionally ignored. @@ -311,6 +321,7 @@ void ExceptionHandler::RestoreHandlersLocked() { return; for (int i = 0; i < kNumHandledSignals; ++i) { + SKIP_SIGILL(kExceptionSignals[i]); if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) { InstallDefaultHandler(kExceptionSignals[i]); } From 98a3f6c5a3239130363d5b31b6b95bc68e0a121a Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Mon, 24 Sep 2018 17:33:17 +0000 Subject: [PATCH 14/25] Bug 1367160 - Refactor to support multiple menu items in the root menu r=mixedpuppy This refactor is mainly meant to support bug 1367160, but it also eases the implementation of bug 1294429, bug 1325758 or bug 1370735 if we ever decide to fix those bugs. Previously, the menu was constructed by creating one root menu item and moving the submenu to the root if there was only one item. The refactored code constructs the menu items by generating a list of (potentially more than one) top-level menu items, and moving excess menu items to a submenu (as before). Besides the ability to support an arbitrary number of top-level menu items, this new implementation also makes it easier to insert menu items and optionally separators at arbitrary locations in the menu. The refactored code obsoletes some separate code paths for browserAction and pageAction menus, which improves the maintainability of the code. There are two user-visible functional changes in this commit: - Excess action menu items (more than ACTION_MENU_TOP_LEVEL_LIMIT = 6) are not silently discarded, but shown in a submenu. See updated test. - menus.onShown/onHidden are now always fired when the action menu is shown, even if the extension has not created any action menu items. Differential Revision: https://phabricator.services.mozilla.com/D6621 --HG-- extra : moz-landing-system : lando --- .../components/extensions/parent/ext-menus.js | 197 ++++++++---------- .../test/browser/browser_ext_menus.js | 13 +- 2 files changed, 94 insertions(+), 116 deletions(-) diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index 1fabbf75bc1c..7ac83d611e70 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -51,40 +51,45 @@ var gMenuBuilder = { xulMenu.addEventListener("popuphidden", this); this.xulMenu = xulMenu; for (let [, root] of gRootItems) { - let rootElement = this.createTopLevelElement(root, contextData); - if (rootElement) { - this.appendTopLevelElement(rootElement); - } + this.createAndInsertTopLevelElements(root, contextData, null); } this.afterBuildingMenu(contextData); }, - // Builds a context menu for browserAction and pageAction buttons. - buildActionContextMenu(contextData) { - const {menu} = contextData; + createAndInsertTopLevelElements(root, contextData, nextSibling) { + let rootElements; + if (contextData.onBrowserAction || contextData.onPageAction) { + if (contextData.extension.id !== root.extension.id) { + return; + } + rootElements = this.buildTopLevelElements(root, contextData, ACTION_MENU_TOP_LEVEL_LIMIT, false); - const root = gRootItems.get(contextData.extension); - if (!root) { + // Action menu items are prepended to the menu, followed by a separator. + nextSibling = nextSibling || this.xulMenu.firstElementChild; + if (rootElements.length && !this.itemsToCleanUp.has(nextSibling)) { + rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator")); + } + } else { + rootElements = this.buildTopLevelElements(root, contextData, 1, true); + if (rootElements.length && !this.itemsToCleanUp.has(this.xulMenu.lastElementChild)) { + // All extension menu items are appended at the end. + // Prepend separator if this is the first extension menu item. + rootElements.unshift(this.xulMenu.ownerDocument.createXULElement("menuseparator")); + } + } + + if (!rootElements.length) { return; } - const children = this.buildChildren(root, contextData); - const visible = children.slice(0, ACTION_MENU_TOP_LEVEL_LIMIT); - - this.xulMenu = menu; - menu.addEventListener("popuphidden", this); - - if (visible.length) { - const separator = menu.ownerDocument.createXULElement("menuseparator"); - menu.insertBefore(separator, menu.firstElementChild); - this.itemsToCleanUp.add(separator); - - for (const child of visible) { - this.itemsToCleanUp.add(child); - menu.insertBefore(child, separator); - } + if (nextSibling) { + nextSibling.before(...rootElements); + } else { + this.xulMenu.append(...rootElements); + } + for (let item of rootElements) { + this.itemsToCleanUp.add(item); } - this.afterBuildingMenu(contextData); }, buildElementWithChildren(item, contextData) { @@ -116,63 +121,58 @@ var gMenuBuilder = { return children; }, - createTopLevelElement(root, contextData) { - let rootElement = this.buildElementWithChildren(root, contextData); - if (!rootElement.firstElementChild || !rootElement.firstElementChild.children.length) { - // If the root has no visible children, there is no reason to show - // the root menu item itself either. - return null; - } - rootElement.setAttribute("ext-type", "top-level-menu"); - rootElement = this.removeTopLevelMenuIfNeeded(rootElement); + buildTopLevelElements(root, contextData, maxCount, forceManifestIcons) { + let children = this.buildChildren(root, contextData); - // Display the extension icon on the root element. - if (root.extension.manifest.icons) { - this.setMenuItemIcon(rootElement, root.extension, contextData, root.extension.manifest.icons); - } else { - this.removeMenuItemIcon(rootElement); - } - return rootElement; - }, - - appendTopLevelElement(rootElement) { - if (this.itemsToCleanUp.size === 0) { - const separator = this.xulMenu.ownerDocument.createXULElement("menuseparator"); - this.itemsToCleanUp.add(separator); - this.xulMenu.append(separator); + // TODO: Fix bug 1492969 and remove this whole if block. + if (children.length === 1 && maxCount === 1 && forceManifestIcons && + AppConstants.platform === "linux" && + children[0].getAttribute("type") === "checkbox") { + // Keep single checkbox items in the submenu on Linux since + // the extension icon overlaps the checkbox otherwise. + maxCount = 0; } - this.xulMenu.appendChild(rootElement); - this.itemsToCleanUp.add(rootElement); + if (children.length > maxCount) { + // Move excess items into submenu. + let rootElement = this.buildSingleElement(root, contextData); + rootElement.setAttribute("ext-type", "top-level-menu"); + rootElement.firstElementChild.append(...children.splice(maxCount - 1)); + children.push(rootElement); + } + + if (forceManifestIcons) { + for (let rootElement of children) { + // Display the extension icon on the root element. + if (root.extension.manifest.icons) { + this.setMenuItemIcon(rootElement, root.extension, contextData, root.extension.manifest.icons); + } else { + this.removeMenuItemIcon(rootElement); + } + } + } + return children; }, removeSeparatorIfNoTopLevelItems() { - if (this.itemsToCleanUp.size === 1) { - // Remove the separator if all extension menu items have disappeared. - const separator = this.itemsToCleanUp.values().next().value; - separator.remove(); - this.itemsToCleanUp.clear(); - } - }, + // Extension menu items always have have a non-empty ID. + let isNonExtensionSeparator = + item => item.nodeName === "menuseparator" && !item.id; - removeTopLevelMenuIfNeeded(element) { - // If there is only one visible top level element we don't need the - // root menu element for the extension. - let menuPopup = element.firstElementChild; - if (menuPopup && menuPopup.children.length == 1) { - let onlyChild = menuPopup.firstElementChild; + // itemsToCleanUp contains all top-level menu items. A separator should + // only be kept if it is next to an extension menu item. + let isExtensionMenuItemSibling = + item => item && this.itemsToCleanUp.has(item) && !isNonExtensionSeparator(item); - // Keep single checkbox items in the submenu on Linux since - // the extension icon overlaps the checkbox otherwise. - if (AppConstants.platform === "linux" && onlyChild.getAttribute("type") === "checkbox") { - return element; + for (let item of this.itemsToCleanUp) { + if (isNonExtensionSeparator(item)) { + if (!isExtensionMenuItemSibling(item.previousElementSibling) && + !isExtensionMenuItemSibling(item.nextElementSibling)) { + item.remove(); + this.itemsToCleanUp.delete(item); + } } - - onlyChild.remove(); - return onlyChild; } - - return element; }, buildSingleElement(item, contextData) { @@ -377,56 +377,27 @@ var gMenuBuilder = { return; } - if (contextData.onBrowserAction || contextData.onPageAction) { - if (contextData.extension.id !== extension.id) { - // The extension that just called refresh() is not the owner of the - // action whose context menu is showing, so it can't have any items in - // the menu anyway and nothing will change. - return; - } - // The action menu can only have items from one extension, so remove all - // items (including the separator) and rebuild the action menu (if any). - for (let item of this.itemsToCleanUp) { - item.remove(); - } - this.itemsToCleanUp.clear(); - this.buildActionContextMenu(contextData); - return; - } - - // First find the one and only top-level menu item for the extension. + // Find the group of existing top-level items (usually 0 or 1 items) + // and remember its position for when the new items are inserted. let elementIdPrefix = `${makeWidgetId(extension.id)}-menuitem-`; - let oldRoot = null; - for (let item = this.xulMenu.lastElementChild; item !== null; item = item.previousElementSibling) { + let nextSibling = null; + for (let item of this.itemsToCleanUp) { if (item.id && item.id.startsWith(elementIdPrefix)) { - oldRoot = item; - this.itemsToCleanUp.delete(oldRoot); - break; + nextSibling = item.nextSibling; + item.remove(); + this.itemsToCleanUp.delete(item); } } let root = gRootItems.get(extension); - let newRoot = root && this.createTopLevelElement(root, contextData); - if (newRoot) { - this.itemsToCleanUp.add(newRoot); - if (oldRoot) { - oldRoot.replaceWith(newRoot); - } else { - this.appendTopLevelElement(newRoot); - } - } else if (oldRoot) { - oldRoot.remove(); - this.removeSeparatorIfNoTopLevelItems(); + if (root) { + this.createAndInsertTopLevelElements(root, contextData, nextSibling); } + this.removeSeparatorIfNoTopLevelItems(); }, + // This should be called once, after constructing the top-level menus, if any. afterBuildingMenu(contextData) { - if (this.contextData) { - // rebuildMenu can trigger us again, but the logic below should run only - // once per open menu. - return; - } - function dispatchOnShownEvent(extension) { // Note: gShownMenuItems is a DefaultMap, so .get(extension) causes the // extension to be stored in the map even if there are currently no @@ -472,7 +443,7 @@ var gMenuBuilder = { global.actionContextMenu = function(contextData) { contextData.tab = tabTracker.activeTab; contextData.pageUrl = contextData.tab.linkedBrowser.currentURI.spec; - gMenuBuilder.buildActionContextMenu(contextData); + gMenuBuilder.build(contextData); }; const contextsMap = { diff --git a/browser/components/extensions/test/browser/browser_ext_menus.js b/browser/components/extensions/test/browser/browser_ext_menus.js index 2ad8870ec8c7..b484a42729a4 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus.js +++ b/browser/components/extensions/test/browser/browser_ext_menus.js @@ -96,11 +96,18 @@ add_task(async function test_actionContextMenus() { is(second.label, "click 1", "Second menu item title is correct"); is(second.id, `${idPrefix}1`, "Second menu item id is correct"); - is(last.label, "click 5", "Last menu item title is correct"); - is(last.id, `${idPrefix}5`, "Last menu item id is correct"); + is(last.tagName, "menu", "Last menu item type is correct"); + is(last.label, "Generated extension", "Last menu item title is correct"); + is(last.getAttribute("ext-type"), "top-level-menu", "Last menu ext-type is correct"); is(separator.tagName, "menuseparator", "Separator after last menu item"); - await closeActionContextMenu(popup.firstElementChild, kind); + // Verify that menu items exceeding ACTION_MENU_TOP_LEVEL_LIMIT are moved into a submenu. + let overflowPopup = await openSubmenu(last); + is(overflowPopup.children.length, 4, "Excess items should be moved into a submenu"); + is(overflowPopup.firstElementChild.id, `${idPrefix}5`, "First submenu item ID is correct"); + is(overflowPopup.lastElementChild.id, `${idPrefix}8`, "Last submenu item ID is correct"); + + await closeActionContextMenu(overflowPopup.firstElementChild, kind); const {info, tab} = await extension.awaitMessage("click"); is(info.pageUrl, "http://example.com/", "Click info pageUrl is correct"); is(tab.id, tabId, "Click event tab ID is correct"); From ee0928a251ef87b3c081e33b480c477625949868 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 25 Sep 2018 16:41:47 +0000 Subject: [PATCH 15/25] Bug 1367160 - Allow extensions to hide default menu items r=mixedpuppy The new method allows extensions to modify menu items in their own moz-extension:-pages, with the following features: - All matching extension items are shown in the root menu (instead of being moved into a submenu), above other menu items, if any. - The icons for these menu items are customizable. - Optionally, the default menu items (including those from other extensions) can be hidden. Depends on D6621 Differential Revision: https://phabricator.services.mozilla.com/D6622 --HG-- extra : moz-landing-system : lando --- browser/actors/ContextMenuChild.jsm | 2 + browser/base/content/nsContextMenu.js | 4 +- .../components/extensions/child/ext-menus.js | 45 +++++++++++++++++++ .../components/extensions/parent/ext-menus.js | 35 ++++++++++++++- .../components/extensions/schemas/menus.json | 19 ++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm index 2a8b2cfd808e..3e3498c12925 100644 --- a/browser/actors/ContextMenuChild.jsm +++ b/browser/actors/ContextMenuChild.jsm @@ -604,6 +604,8 @@ class ContextMenuChild extends ActorChild { parentAllowsMixedContent, }; + Services.obs.notifyObservers({wrappedJSObject: data}, "on-prepare-contextmenu"); + if (isRemote) { this.mm.sendAsyncMessage("contextmenu", data, { targetAsCPOW, diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 51bdbbb18263..fa779b7e3c7f 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -65,6 +65,7 @@ function openContextMenu(aMessage) { loginFillInfo: data.loginFillInfo, parentAllowsMixedContent: data.parentAllowsMixedContent, userContextId: data.userContextId, + webExtContextData: data.webExtContextData, }; let popup = browser.ownerDocument.getElementById("contentAreaContextMenu"); @@ -128,6 +129,7 @@ nsContextMenu.prototype = { linkUrl: this.linkURL, selectionText: this.isTextSelected ? this.selectionInfo.fullText : undefined, frameId: this.frameOuterWindowID, + webExtContextData: gContextMenuContentData ? gContextMenuContentData.webExtContextData : undefined, }; subject.wrappedJSObject = subject; Services.obs.notifyObservers(subject, "on-build-contextmenu"); @@ -167,7 +169,7 @@ nsContextMenu.prototype = { this.shouldDisplay = context.shouldDisplay; this.timeStamp = context.timeStamp; - // Assign what's _possibly_ needed from `context` sent by ContextMenu.jsm + // Assign what's _possibly_ needed from `context` sent by ContextMenuChild.jsm // Keep this consistent with the similar code in ContextMenu's _setContext this.bgImageURL = context.bgImageURL; this.imageDescURL = context.imageDescURL; diff --git a/browser/components/extensions/child/ext-menus.js b/browser/components/extensions/child/ext-menus.js index ee204036e9a9..4ff42c6d2b2c 100644 --- a/browser/components/extensions/child/ext-menus.js +++ b/browser/components/extensions/child/ext-menus.js @@ -2,10 +2,17 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; +ChromeUtils.defineModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + var { withHandlingUserInput, } = ExtensionCommon; +var { + ExtensionError, +} = ExtensionUtils; + // If id is not specified for an item we use an integer. // This ID need only be unique within a single addon. Since all addon code that // can use this API runs in the same process, this local variable suffices. @@ -108,6 +115,7 @@ class ContextMenusClickPropHandler { this.menusInternal = class extends ExtensionAPI { getAPI(context) { let onClickedProp = new ContextMenusClickPropHandler(context); + let pendingMenuEvent; let api = { menus: { @@ -165,6 +173,43 @@ this.menusInternal = class extends ExtensionAPI { return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []); }, + overrideContext(contextOptions) { + let {event} = context.contentWindow; + if (!event || event.type !== "contextmenu" || !event.isTrusted) { + throw new ExtensionError("overrideContext must be called during a \"contextmenu\" event"); + } + + let webExtContextData = { + extensionId: context.extension.id, + showDefaults: contextOptions.showDefaults, + }; + + if (pendingMenuEvent) { + // overrideContext is called more than once during the same event. + pendingMenuEvent.webExtContextData = webExtContextData; + return; + } + pendingMenuEvent = { + webExtContextData, + observe(subject, topic, data) { + pendingMenuEvent = null; + Services.obs.removeObserver(this, "on-prepare-contextmenu"); + subject.wrappedJSObject.webExtContextData = this.webExtContextData; + }, + run() { + // "on-prepare-contextmenu" is expected to be observed before the + // end of the "contextmenu" event dispatch. This task is queued + // in case that does not happen, e.g. when the menu is not shown. + if (pendingMenuEvent === this) { + pendingMenuEvent = null; + Services.obs.removeObserver(this, "on-prepare-contextmenu"); + } + }, + }; + Services.obs.addObserver(pendingMenuEvent, "on-prepare-contextmenu"); + Services.tm.dispatchToMainThread(pendingMenuEvent); + }, + onClicked: new EventManager({ context, name: "menus.onClicked", diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index 7ac83d611e70..5c87cd9198af 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -54,6 +54,12 @@ var gMenuBuilder = { this.createAndInsertTopLevelElements(root, contextData, null); } this.afterBuildingMenu(contextData); + + if (contextData.webExtContextData && !contextData.webExtContextData.showDefaults) { + // Wait until nsContextMenu.js has toggled the visibility of the default + // menu items before hiding the default items. + Promise.resolve().then(() => this.hideDefaultMenuItems()); + } }, createAndInsertTopLevelElements(root, contextData, nextSibling) { @@ -69,7 +75,26 @@ var gMenuBuilder = { if (rootElements.length && !this.itemsToCleanUp.has(nextSibling)) { rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator")); } - } else { + } else if (contextData.webExtContextData) { + let { + extensionId, + showDefaults, + } = contextData.webExtContextData; + if (extensionId === root.extension.id) { + rootElements = this.buildTopLevelElements(root, contextData, Infinity, false); + // The extension menu should be rendered at the top, but after the navigation buttons. + nextSibling = nextSibling || this.xulMenu.querySelector(":scope > #context-sep-navigation + *"); + if (rootElements.length && showDefaults && !this.itemsToCleanUp.has(nextSibling)) { + rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator")); + } + } else if (!showDefaults) { + // When the default menu items should be hidden, menu items from other + // extensions should be hidden too. + return; + } + // Fall through to show default extension menu items. + } + if (!rootElements) { rootElements = this.buildTopLevelElements(root, contextData, 1, true); if (rootElements.length && !this.itemsToCleanUp.has(this.xulMenu.lastElementChild)) { // All extension menu items are appended at the end. @@ -416,6 +441,14 @@ var gMenuBuilder = { this.contextData = contextData; }, + hideDefaultMenuItems() { + for (let item of this.xulMenu.children) { + if (!this.itemsToCleanUp.has(item)) { + item.hidden = true; + } + } + }, + handleEvent(event) { if (this.xulMenu != event.target || event.type != "popuphidden") { return; diff --git a/browser/components/extensions/schemas/menus.json b/browser/components/extensions/schemas/menus.json index 8618f4287e5f..12898564f258 100644 --- a/browser/components/extensions/schemas/menus.json +++ b/browser/components/extensions/schemas/menus.json @@ -402,6 +402,25 @@ } ] }, + { + "name": "overrideContext", + "type": "function", + "description": "Show the matching menu items from this extension instead of the default menu. This should be called during a 'contextmenu' DOM event handler, and only applies to the menu that opens after this event.", + "parameters": [ + { + "name": "contextOptions", + "type": "object", + "properties": { + "showDefaults": { + "type": "boolean", + "optional": true, + "default": false, + "description": "Whether to also include default menu items in the menu." + } + } + } + ] + }, { "name": "refresh", "type": "function", From 3e9552cda502e62fe7787baa5487d8aba8925522 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 25 Sep 2018 12:18:19 +0000 Subject: [PATCH 16/25] Bug 1367160 - Add tests for overriding extension menus r=mixedpuppy Depends on D6622 Differential Revision: https://phabricator.services.mozilla.com/D6623 --HG-- extra : moz-landing-system : lando --- .../test/browser/browser-common.ini | 1 + .../browser/browser_ext_menus_replace_menu.js | 325 ++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js diff --git a/browser/components/extensions/test/browser/browser-common.ini b/browser/components/extensions/test/browser/browser-common.ini index 61b468e304b8..6f0f717e4867 100644 --- a/browser/components/extensions/test/browser/browser-common.ini +++ b/browser/components/extensions/test/browser/browser-common.ini @@ -116,6 +116,7 @@ skip-if = (verify && (os == 'linux' || os == 'mac')) [browser_ext_menus_event_order.js] [browser_ext_menus_events.js] [browser_ext_menus_refresh.js] +[browser_ext_menus_replace_menu.js] [browser_ext_menus_targetElement.js] [browser_ext_menus_targetElement_extension.js] [browser_ext_menus_targetElement_shadow.js] diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js new file mode 100644 index 000000000000..c30f16c9ddfc --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js @@ -0,0 +1,325 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +function getVisibleChildrenIds(menuElem) { + return Array.from(menuElem.children).filter(elem => !elem.hidden).map(elem => elem.id || elem.tagName); +} + +function checkIsDefaultMenuItemVisible(visibleMenuItemIds) { + // In this whole test file, we open a menu on a link. Assume that all + // default menu items are shown if one link-specific menu item is shown. + ok(visibleMenuItemIds.includes("context-openlink"), + `The default 'Open Link in New Tab' menu item should be in ${visibleMenuItemIds}.`); +} + +// Tests the following: +// - Calling overrideContext({}) during oncontextmenu forces the menu to only +// show an extension's own items. +// - These menu items all appear in the root menu. +// - The usual extension filtering behavior (e.g. documentUrlPatterns and +// targetUrlPatterns) is still applied; some menu items are therefore hidden. +// - Calling overrideContext({showDefaults:true}) causes the default menu items +// to be shown, but only after the extension's. +// - overrideContext expires after the menu is opened once. +add_task(async function overrideContext_in_extension_tab() { + function extensionTabScript() { + document.addEventListener("contextmenu", () => { + browser.menus.overrideContext({}); + browser.test.sendMessage("oncontextmenu_in_dom_part_1"); + }, {once: true}); + + browser.menus.create({ + id: "tab_1", + title: "tab_1", + documentUrlPatterns: [document.URL], + onclick() { + document.addEventListener("contextmenu", () => { + // Verifies that last call takes precedence. + browser.menus.overrideContext({showDefaults: false}); + browser.menus.overrideContext({showDefaults: true}); + browser.test.sendMessage("oncontextmenu_in_dom_part_2"); + }, {once: true}); + browser.test.sendMessage("onClicked_tab_1"); + }, + }); + browser.menus.create({ + id: "tab_2", + title: "tab_2", + onclick() { + browser.test.sendMessage("onClicked_tab_2"); + }, + }, () => { + browser.test.sendMessage("menu-registered"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["menus"], + }, + files: { + "tab.html": ` + + Link + + `, + "tab.js": extensionTabScript, + }, + background() { + // Expected to match and thus be visible. + browser.menus.create({id: "bg_1", title: "bg_1"}); + browser.menus.create({id: "bg_2", title: "bg_2", targetUrlPatterns: ["*://example.com/*"]}); + + // Expected to not match and be hidden. + browser.menus.create({id: "bg_3", title: "bg_3", targetUrlPatterns: ["*://nomatch/*"]}); + browser.menus.create({id: "bg_4", title: "bg_4", documentUrlPatterns: [document.URL]}); + + browser.menus.onShown.addListener(info => { + browser.test.assertEq("bg_1,bg_2,tab_1,tab_2", info.menuIds.join(","), "Expected menu items."); + browser.test.sendMessage("onShown"); + }); + + browser.tabs.create({url: "tab.html"}); + }, + }); + + let otherExtension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["menus"], + }, + background() { + browser.menus.create({id: "other_extension_item", title: "other_extension_item"}, () => { + browser.test.sendMessage("other_extension_item_created"); + }); + }, + }); + await otherExtension.startup(); + await otherExtension.awaitMessage("other_extension_item_created"); + + let extensionTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + await extension.startup(); + // Must wait for the tab to have loaded completely before calling openContextMenu. + await extensionTabPromise; + await extension.awaitMessage("menu-registered"); + + const EXPECTED_EXTENSION_MENU_IDS = [ + `${makeWidgetId(extension.id)}-menuitem-_bg_1`, + `${makeWidgetId(extension.id)}-menuitem-_bg_2`, + `${makeWidgetId(extension.id)}-menuitem-_tab_1`, + `${makeWidgetId(extension.id)}-menuitem-_tab_2`, + ]; + const OTHER_EXTENSION_MENU_ID = + `${makeWidgetId(otherExtension.id)}-menuitem-_other_extension_item`; + + { + // Tests overrideContext({}) + info("Expecting the menu to be replaced by overrideContext."); + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom_part_1"); + await extension.awaitMessage("onShown"); + + Assert.deepEqual( + getVisibleChildrenIds(menu), + EXPECTED_EXTENSION_MENU_IDS, + "Expected only extension menu items"); + + let menuItems = menu.getElementsByAttribute("label", "tab_1"); + await closeExtensionContextMenu(menuItems[0]); + await extension.awaitMessage("onClicked_tab_1"); + } + + { + // Tests overrideContext({showDefaults:true})) + info("Expecting the menu to be replaced by overrideContext, including default menu items."); + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom_part_2"); + await extension.awaitMessage("onShown"); + + let visibleMenuItemIds = getVisibleChildrenIds(menu); + Assert.deepEqual( + visibleMenuItemIds.slice(0, EXPECTED_EXTENSION_MENU_IDS.length), + EXPECTED_EXTENSION_MENU_IDS, + "Expected extension menu items at the start."); + + checkIsDefaultMenuItemVisible(visibleMenuItemIds); + + is(visibleMenuItemIds[visibleMenuItemIds.length - 1], OTHER_EXTENSION_MENU_ID, + "Other extension menu item should be at the end."); + + let menuItems = menu.getElementsByAttribute("label", "tab_2"); + await closeExtensionContextMenu(menuItems[0]); + await extension.awaitMessage("onClicked_tab_2"); + } + + { + // Tests that previous overrideContext call has been forgotten, + // so the default behavior should occur (=move items into submenu). + info("Expecting the default menu to be used when overrideContext is not called."); + let menu = await openContextMenu("a"); + await extension.awaitMessage("onShown"); + + checkIsDefaultMenuItemVisible(getVisibleChildrenIds(menu)); + + let menuItems = menu.getElementsByAttribute("ext-type", "top-level-menu"); + is(menuItems.length, 1, "Expected top-level menu element for extension."); + let topLevelExtensionMenuItem = menuItems[0]; + is(topLevelExtensionMenuItem.nextSibling, null, "Extension menu should be the last element."); + + const submenu = await openSubmenu(topLevelExtensionMenuItem); + is(submenu, topLevelExtensionMenuItem.firstElementChild, "Correct submenu opened"); + + Assert.deepEqual( + getVisibleChildrenIds(submenu), + EXPECTED_EXTENSION_MENU_IDS, + "Extension menu items should be in the submenu by default."); + + await closeContextMenu(); + } + + // Unloading the extension will automatically close the extension's tab.html + await extension.unload(); + await otherExtension.unload(); +}); + +// Tests some edge cases: +// - overrideContext() is called without any menu registrations, +// followed by a menu registration + menus.refresh.. +// - overrideContext() is called and event.preventDefault() is also +// called to stop the menu from appearing. +// - Open menu again and verify that the default menu behavior occurs. +add_task(async function overrideContext_sidebar_edge_cases() { + function sidebarJs() { + const TIME_BEFORE_MENU_SHOWN = Date.now(); + let count = 0; + // eslint-disable-next-line mozilla/balanced-listeners + document.addEventListener("contextmenu", event => { + ++count; + if (count === 1) { + browser.menus.overrideContext({}); + } else if (count === 2) { + browser.menus.overrideContext({}); + event.preventDefault(); // Prevent menu from being shown. + + // We are not expecting a menu. Wait for the time it took to show and + // hide the previous menu, to check that no new menu appears. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + browser.test.sendMessage("stop_waiting_for_menu_shown", "timer_reached"); + }, Date.now() - TIME_BEFORE_MENU_SHOWN); + } else if (count === 3) { + // The overrideContext from the previous call should be forgotten. + // Use the default behavior, i.e. show the default menu. + } else { + browser.test.fail(`Unexpected menu count: ${count}`); + } + + browser.test.sendMessage("oncontextmenu_in_dom"); + }); + + browser.menus.onShown.addListener(info => { + if (count === 1) { + browser.test.assertEq("", info.menuIds.join(","), "Expected no items"); + browser.menus.create({id: "some_item", title: "some_item"}, () => { + browser.test.sendMessage("onShown_1_and_menu_item_created"); + }); + } else if (count === 2) { + browser.test.fail("onShown should not have fired when the menu is not shown."); + } else if (count === 3) { + browser.test.assertEq("some_item", info.menuIds.join(","), "Expected menu item"); + browser.test.sendMessage("onShown_3"); + } else { + browser.test.fail(`Unexpected onShown at count: ${count}`); + } + }); + + browser.test.onMessage.addListener(async msg => { + browser.test.assertEq("refresh_menus", msg, "Expected message"); + browser.test.assertEq(1, count, "Expected at first menu test"); + await browser.menus.refresh(); + browser.test.sendMessage("menus_refreshed"); + }); + + browser.menus.onHidden.addListener(() => { + browser.test.sendMessage("onHidden", count); + }); + + browser.test.sendMessage("sidebar_ready"); + } + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", // To automatically show sidebar on load. + manifest: { + permissions: ["menus"], + sidebar_action: { + default_panel: "sidebar.html", + }, + }, + files: { + "sidebar.html": ` + + Link + + `, + "sidebar.js": sidebarJs, + }, + background() { + browser.test.assertThrows( + () => { browser.menus.overrideContext({someInvalidParameter: true}); }, + /Unexpected property "someInvalidParameter"/, + "overrideContext should be available and the parameters be validated."); + browser.test.assertThrows( + () => { browser.menus.overrideContext({}); }, + /overrideContext must be called during a "contextmenu" event/, + "overrideContext should fail outside of a 'contextmenu' event."); + browser.test.sendMessage("bg_test_done"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("bg_test_done"); + await extension.awaitMessage("sidebar_ready"); + + const EXPECTED_EXTENSION_MENU_ID = + `${makeWidgetId(extension.id)}-menuitem-_some_item`; + + { + // Checks that a menu can initially be empty and be updated. + info("Expecting menu without items to appear and be updated after menus.refresh()"); + let menu = await openContextMenuInSidebar("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + await extension.awaitMessage("onShown_1_and_menu_item_created"); + Assert.deepEqual(getVisibleChildrenIds(menu), [], "Expected no items, initially"); + extension.sendMessage("refresh_menus"); + await extension.awaitMessage("menus_refreshed"); + Assert.deepEqual(getVisibleChildrenIds(menu), [EXPECTED_EXTENSION_MENU_ID], "Expected updated menu"); + await closeContextMenu(menu); + is(await extension.awaitMessage("onHidden"), 1, "Menu hidden"); + } + + { + // Trigger a context menu. The page has prevented the menu from being + // shown, so the promise should not resolve. + info("Expecting menu to not appear because of event.preventDefault()"); + let popupShowingPromise = openContextMenuInSidebar("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + is(await Promise.race([ + extension.awaitMessage("stop_waiting_for_menu_shown"), + popupShowingPromise.then(() => "popup_shown"), + ]), "timer_reached", "The menu should not be shown."); + } + + { + info("Expecting default menu to be shown when the menu is reopened after event.preventDefault()"); + let menu = await openContextMenuInSidebar("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + await extension.awaitMessage("onShown_3"); + let visibleMenuItemIds = getVisibleChildrenIds(menu); + checkIsDefaultMenuItemVisible(visibleMenuItemIds); + ok(visibleMenuItemIds.includes(EXPECTED_EXTENSION_MENU_ID), "Expected extension menu item"); + await closeContextMenu(menu); + is(await extension.awaitMessage("onHidden"), 3, "Menu hidden"); + } + + await extension.unload(); +}); From 793920720955ca1c088009b11912f44f38485cb2 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 27 Sep 2018 08:21:05 +0000 Subject: [PATCH 17/25] Bug 1280347 - Support changing context type to tab and bookmark r=mixedpuppy This allows extensions to include tab/bookmark menu items from other extensions. Built-in menu items from the browser are *not* added. Depends on D6623 Differential Revision: https://phabricator.services.mozilla.com/D6624 --HG-- extra : moz-landing-system : lando --- .../components/extensions/child/ext-menus.js | 30 ++++++++++++++++++ .../components/extensions/parent/ext-menus.js | 31 ++++++++++++++++++- .../components/extensions/schemas/menus.json | 18 +++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/browser/components/extensions/child/ext-menus.js b/browser/components/extensions/child/ext-menus.js index 4ff42c6d2b2c..8790a75abe45 100644 --- a/browser/components/extensions/child/ext-menus.js +++ b/browser/components/extensions/child/ext-menus.js @@ -179,9 +179,39 @@ this.menusInternal = class extends ExtensionAPI { throw new ExtensionError("overrideContext must be called during a \"contextmenu\" event"); } + let checkValidArg = (contextType, propKey) => { + if (contextOptions.context !== contextType) { + if (contextOptions[propKey]) { + throw new ExtensionError(`Property "${propKey}" can only be used with context "${contextType}"`); + } + return false; + } + if (contextOptions.showDefaults) { + throw new ExtensionError(`Property "showDefaults" cannot be used with context "${contextType}"`); + } + if (!contextOptions[propKey]) { + throw new ExtensionError(`Property "${propKey}" is required for context "${contextType}"`); + } + return true; + }; + if (checkValidArg("tab", "tabId")) { + if (!context.extension.hasPermission("tabs") || + !context.extension.whiteListedHosts.subsumes(new MatchPattern(""))) { + throw new ExtensionError(`The "tab" context requires the "tabs" and "" permission.`); + } + } + if (checkValidArg("bookmark", "bookmarkId")) { + if (!context.extension.hasPermission("bookmarks")) { + throw new ExtensionError(`The "bookmark" context requires the "bookmarks" permission.`); + } + } + let webExtContextData = { extensionId: context.extension.id, showDefaults: contextOptions.showDefaults, + overrideContext: contextOptions.context, + bookmarkId: contextOptions.bookmarkId, + tabId: contextOptions.tabId, }; if (pendingMenuEvent) { diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index 5c87cd9198af..27a473a758d9 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -47,6 +47,7 @@ var gMenuBuilder = { // to be displayed. We always clear all the items again when // popuphidden fires. build(contextData) { + contextData = this.maybeOverrideContextData(contextData); let xulMenu = contextData.menu; xulMenu.addEventListener("popuphidden", this); this.xulMenu = xulMenu; @@ -62,6 +63,33 @@ var gMenuBuilder = { } }, + maybeOverrideContextData(contextData) { + let {webExtContextData} = contextData; + if (!webExtContextData || !webExtContextData.overrideContext) { + return contextData; + } + if (webExtContextData.overrideContext === "bookmark") { + return { + menu: contextData.menu, + bookmarkId: webExtContextData.bookmarkId, + onBookmark: true, + webExtContextData, + }; + } + if (webExtContextData.overrideContext === "tab") { + // TODO: Handle invalid tabs more gracefully (instead of throwing). + let tab = tabTracker.getTab(webExtContextData.tabId); + return { + menu: contextData.menu, + tab, + pageUrl: tab.linkedBrowser.currentURI.spec, + onTab: true, + webExtContextData, + }; + } + throw new Error(`Unexpected overrideContext: ${webExtContextData.overrideContext}`); + }, + createAndInsertTopLevelElements(root, contextData, nextSibling) { let rootElements; if (contextData.onBrowserAction || contextData.onPageAction) { @@ -79,6 +107,7 @@ var gMenuBuilder = { let { extensionId, showDefaults, + overrideContext, } = contextData.webExtContextData; if (extensionId === root.extension.id) { rootElements = this.buildTopLevelElements(root, contextData, Infinity, false); @@ -87,7 +116,7 @@ var gMenuBuilder = { if (rootElements.length && showDefaults && !this.itemsToCleanUp.has(nextSibling)) { rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator")); } - } else if (!showDefaults) { + } else if (!showDefaults && !overrideContext) { // When the default menu items should be hidden, menu items from other // extensions should be hidden too. return; diff --git a/browser/components/extensions/schemas/menus.json b/browser/components/extensions/schemas/menus.json index 12898564f258..2fbafd797ce6 100644 --- a/browser/components/extensions/schemas/menus.json +++ b/browser/components/extensions/schemas/menus.json @@ -416,6 +416,24 @@ "optional": true, "default": false, "description": "Whether to also include default menu items in the menu." + }, + "context": { + "type": "string", + "enum": ["bookmark", "tab"], + "optional": true, + "description": "ContextType to override, to allow menu items from other extensions in the menu. Currently only 'bookmark' and 'tab' are supported. showDefaults cannot be used with this option." + }, + "bookmarkId": { + "type": "string", + "minLength": 1, + "optional": true, + "description": "Required when context is 'bookmark'. Requires 'bookmark' permission." + }, + "tabId": { + "type": "integer", + "minimum": 0, + "optional": true, + "description": "Required when context is 'tab'. Requires 'tabs' and '' permission." } } } From 444c304d8b2a9de3a41d2d229a5287a5cfac75d0 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 27 Sep 2018 08:21:06 +0000 Subject: [PATCH 18/25] Bug 1280347 - Add tests for overriding context type of extension menus r=mixedpuppy Depends on D6624 Differential Revision: https://phabricator.services.mozilla.com/D6625 --HG-- extra : moz-landing-system : lando --- .../test/browser/browser-common.ini | 2 + .../browser_ext_menus_replace_menu_context.js | 208 ++++++++++++++++++ ...wser_ext_menus_replace_menu_permissions.js | 170 ++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js create mode 100644 browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js diff --git a/browser/components/extensions/test/browser/browser-common.ini b/browser/components/extensions/test/browser/browser-common.ini index 6f0f717e4867..e289c6a875b0 100644 --- a/browser/components/extensions/test/browser/browser-common.ini +++ b/browser/components/extensions/test/browser/browser-common.ini @@ -117,6 +117,8 @@ skip-if = (verify && (os == 'linux' || os == 'mac')) [browser_ext_menus_events.js] [browser_ext_menus_refresh.js] [browser_ext_menus_replace_menu.js] +[browser_ext_menus_replace_menu_context.js] +[browser_ext_menus_replace_menu_permissions.js] [browser_ext_menus_targetElement.js] [browser_ext_menus_targetElement_extension.js] [browser_ext_menus_targetElement_shadow.js] diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js new file mode 100644 index 000000000000..af86234b7127 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js @@ -0,0 +1,208 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +function getVisibleChildrenIds(menuElem) { + return Array.from(menuElem.children).filter(elem => !elem.hidden).map(elem => elem.id || elem.tagName); +} + +function checkIsDefaultMenuItemVisible(visibleMenuItemIds) { + // In this whole test file, we open a menu on a link. Assume that all + // default menu items are shown if one link-specific menu item is shown. + ok(visibleMenuItemIds.includes("context-openlink"), + `The default 'Open Link in New Tab' menu item should be in ${visibleMenuItemIds}.`); +} + +// Tests that the context of an extension menu can be changed to: +// - tab +// - bookmark +add_task(async function overrideContext_with_context() { + // Background script of the main test extension and the auxilary other extension. + function background() { + browser.test.onMessage.addListener(async (msg, tabId) => { + browser.test.assertEq("testTabAccess", msg, `Expected message in ${browser.runtime.id}`); + let tab = await browser.tabs.get(tabId); + if (!tab.url) { // tabs or activeTab not active. + browser.test.sendMessage("testTabAccessDone", false); + return; + } + try { + let [url] = await browser.tabs.executeScript(tabId, { + code: "document.URL", + }); + browser.test.assertEq("http://example.com/?SomeTab", url, "Expected successful executeScript"); + } catch (e) { + browser.test.fail(`Failed to execute script at ${tabId} (${tab.url}): ${e}`); + } + browser.test.sendMessage("testTabAccessDone", true); + }); + browser.menus.onShown.addListener((info, tab) => { + browser.test.sendMessage("onShown", { + menuIds: info.menuIds, + contexts: info.contexts, + bookmarkId: info.bookmarkId, + tabId: tab && tab.id, + }); + }); + browser.menus.onClicked.addListener((info, tab) => { + browser.test.sendMessage("onClicked", { + menuItemId: info.menuItemId, + bookmarkId: info.bookmarkId, + tabId: tab && tab.id, + }); + }); + browser.menus.create({id: "tab_context", title: "tab_context", contexts: ["tab"]}); + browser.menus.create({id: "bookmark_context", title: "bookmark_context", contexts: ["bookmark"]}); + browser.menus.create({id: "link_context", title: "link_context"}, () => { + browser.test.sendMessage("menu_items_registered"); + }); + + if (browser.runtime.id === "@menu-test-extension") { + browser.tabs.create({url: "tab.html"}); + } + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: {gecko: {id: "@menu-test-extension"}}, + permissions: ["menus", "tabs", "bookmarks", ""], + }, + files: { + "tab.html": ` + + Link + + `, + "tab.js": async () => { + let [tab] = await browser.tabs.query({ + url: "http://example.com/?SomeTab", + }); + let bookmark = await browser.bookmarks.create({ + title: "Bookmark for menu test", + url: "http://example.com/bookmark", + }); + let testCases = [{ + context: "tab", + tabId: tab.id, + }, { + context: "bookmark", + bookmarkId: bookmark.id, + }, { + context: "tab", + tabId: 123456789, // Some invalid tabId. + }]; + + // eslint-disable-next-line mozilla/balanced-listeners + document.addEventListener("contextmenu", () => { + browser.menus.overrideContext(testCases.shift()); + browser.test.sendMessage("oncontextmenu_in_dom"); + }); + + browser.test.sendMessage("setup_ready", { + bookmarkId: bookmark.id, + tabId: tab.id, + }); + }, + }, + background, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?SomeTab"); + + let otherExtension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: {gecko: {id: "@other-test-extension"}}, + permissions: ["menus", "bookmarks", "activeTab"], + }, + background, + }); + await otherExtension.startup(); + await otherExtension.awaitMessage("menu_items_registered"); + + await extension.startup(); + await extension.awaitMessage("menu_items_registered"); + + let {bookmarkId, tabId} = await extension.awaitMessage("setup_ready"); + info(`Set up test with tabId=${tabId} and bookmarkId=${bookmarkId}.`); + + { + // Test case 1: context=tab + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + for (let ext of [extension, otherExtension]) { + info(`Testing menu from ${ext.id} after changing context to tab`); + Assert.deepEqual(await ext.awaitMessage("onShown"), { + menuIds: ["tab_context"], + contexts: ["tab"], + bookmarkId: undefined, + tabId, + }, "Expected onShown details after changing context to tab"); + } + Assert.deepEqual(getVisibleChildrenIds(menu), [ + `${makeWidgetId(extension.id)}-menuitem-_tab_context`, + `menuseparator`, + `${makeWidgetId(otherExtension.id)}-menuitem-_tab_context`, + ], "Expected menu items after changing context to tab"); + + // Extension should already be able to execute script due to host permissions. + extension.sendMessage("testTabAccess", tabId); + ok(await extension.awaitMessage("testTabAccessDone"), + "Extension has access via permissions"); + + otherExtension.sendMessage("testTabAccess", tabId); + isnot(await otherExtension.awaitMessage("testTabAccessDone"), + "Other extension should not have activeTab permissions yet."); + + // Click on the menu item of the other extension to unlock host permissions. + let menuItems = menu.getElementsByAttribute("label", "tab_context"); + is(menuItems.length, 2, "There are two menu items with label 'tab_context'"); + await closeExtensionContextMenu(menuItems[1]); + + Assert.deepEqual(await otherExtension.awaitMessage("onClicked"), { + menuItemId: "tab_context", + bookmarkId: undefined, + tabId, + }, "Expected onClicked details after changing context to tab"); + + otherExtension.sendMessage("testTabAccess", tabId); + ok(await otherExtension.awaitMessage("testTabAccessDone"), + "Other extension should have activeTab permissions."); + } + + { + // Test case 2: context=bookmark + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + for (let ext of [extension, otherExtension]) { + info(`Testing menu from ${ext.id} after changing context to bookmark`); + let shownInfo = await ext.awaitMessage("onShown"); + Assert.deepEqual(shownInfo, { + menuIds: ["bookmark_context"], + contexts: ["bookmark"], + bookmarkId, + tabId: undefined, + }, "Expected onShown details after changing context to bookmark"); + } + Assert.deepEqual(getVisibleChildrenIds(menu), [ + `${makeWidgetId(extension.id)}-menuitem-_bookmark_context`, + `menuseparator`, + `${makeWidgetId(otherExtension.id)}-menuitem-_bookmark_context`, + ], "Expected menu items after changing context to bookmark"); + await closeContextMenu(menu); + } + + { + // Test case 3: context=tab, invalid tabId. + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + // When an invalid tabId is used, all extension menu logic is skipped and + // the default menu is shown. + checkIsDefaultMenuItemVisible(getVisibleChildrenIds(menu)); + await closeContextMenu(menu); + } + + await extension.unload(); + await otherExtension.unload(); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js new file mode 100644 index 000000000000..4933c906360d --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +add_task(async function auto_approve_optional_permissions() { + // Auto-approve optional permission requests, without UI. + await SpecialPowers.pushPrefEnv({ + set: [["extensions.webextOptionalPermissionPrompts", false]], + }); + // TODO: Consider an observer for "webextension-optional-permission-prompt" + // once bug 1493396 is fixed. +}); + +add_task(async function overrideContext_permissions() { + function sidebarJs() { + // If the extension has the right permissions, calling + // menus.overrideContext with one of the following should not throw. + const CONTEXT_OPTIONS_TAB = {context: "tab", tabId: 1}; + const CONTEXT_OPTIONS_BOOKMARK = {context: "bookmark", bookmarkId: "x"}; + + const E_PERM_TAB = /The "tab" context requires the "tabs" and "" permission/; + const E_PERM_BOOKMARK = /The "bookmark" context requires the "bookmarks" permission/; + + function assertAllowed(contextOptions) { + try { + let result = browser.menus.overrideContext(contextOptions); + browser.test.assertEq(undefined, result, `Allowed menu for context=${contextOptions.context}`); + } catch (e) { + browser.test.fail(`Unexpected error for context=${contextOptions.context}: ${e}`); + } + } + + function assertNotAllowed(contextOptions, expectedError) { + browser.test.assertThrows(() => { + browser.menus.overrideContext(contextOptions); + }, expectedError, `Expected error for context=${contextOptions.context}`); + } + + async function requestPermissions(permissions) { + try { + let permPromise; + window.withHandlingUserInputForPermissionRequestTest(() => { + permPromise = browser.permissions.request(permissions); + }); + browser.test.assertTrue(await permPromise, `Should have granted ${JSON.stringify(permissions)}`); + } catch (e) { + browser.test.fail(`Failed to use permissions.request(${JSON.stringify(permissions)}): ${e}`); + } + } + + // The menus.overrideContext method can only be called during a + // "contextmenu" event. So we use a generator to run tests, and yield + // before we call overrideContext after an asynchronous operation. + let testGenerator = (async function* () { + // context without required property. + browser.test.assertThrows( + () => { browser.menus.overrideContext({context: "tab"}); }, + /Property "tabId" is required for context "tab"/, + "Required property for context tab"); + browser.test.assertThrows( + () => { browser.menus.overrideContext({context: "bookmark"}); }, + /Property "bookmarkId" is required for context "bookmark"/, + "Required property for context bookmarks"); + + // context with too many properties. + browser.test.assertThrows( + () => { browser.menus.overrideContext({context: "bookmark", bookmarkId: "x", tabId: 1}); }, + /Property "tabId" can only be used with context "tab"/, + "Invalid property for context bookmarks"); + browser.test.assertThrows( + () => { browser.menus.overrideContext({context: "bookmark", bookmarkId: "x", showDefaults: true}); }, + /Property "showDefaults" cannot be used with context "bookmark"/, + "showDefaults cannot be used with context bookmark"); + + // context with right properties, but missing permissions. + assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); + assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + + await requestPermissions({permissions: ["bookmarks"]}); + browser.test.log("Active permissions: bookmarks"); + yield; + + assertAllowed(CONTEXT_OPTIONS_BOOKMARK); + assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + + await requestPermissions({permissions: ["tabs"]}); + await browser.permissions.remove({permissions: ["bookmarks"]}); + browser.test.log("Active permissions: tabs"); + yield; + + assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); + // "tabs" permission was granted, but host permissions are required too. + assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + + await requestPermissions({origins: [""]}); + await browser.permissions.remove({permissions: ["tabs"]}); + browser.test.log("Active permissions: "); + yield; + + // Just host permissions are not sufficient for either context. + assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); + assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + + await requestPermissions({permissions: ["tabs"]}); + browser.test.log("Active permissions: , tabs"); + yield; + + assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); + assertAllowed(CONTEXT_OPTIONS_TAB); + await browser.permissions.remove({origins: [""]}); + browser.test.log("Active permissions: tabs without hosts"); + yield; + + // Host permissions were revoked, access should be blocked again. + assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + })(); + + // eslint-disable-next-line mozilla/balanced-listeners + document.addEventListener("contextmenu", async event => { + event.preventDefault(); + try { + let {done} = await testGenerator.next(); + browser.test.sendMessage("continue_test", !done); + } catch (e) { + browser.test.fail(`Unexpected error: ${e} :: ${e.stack}`); + browser.test.sendMessage("continue_test", false); + } + }); + browser.test.sendMessage("sidebar_ready"); + } + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", // To automatically show sidebar on load. + manifest: { + permissions: ["menus"], + optional_permissions: ["tabs", "bookmarks", ""], + sidebar_action: { + default_panel: "sidebar.html", + }, + }, + files: { + "sidebar.html": ` + + Link + + `, + "sidebar.js": sidebarJs, + }, + }); + await extension.startup(); + await extension.awaitMessage("sidebar_ready"); + + // permissions.request requires user input, export helper. + await ContentTask.spawn(SidebarUI.browser.contentDocument.getElementById("webext-panels-browser"), null, () => { + let {withHandlingUserInput} = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm", {}).ExtensionCommon; + Cu.exportFunction((fn) => { + return withHandlingUserInput(content, fn); + }, content, { + defineAs: "withHandlingUserInputForPermissionRequestTest", + }); + }); + + do { + info(`Going to trigger "contextmenu" event.`); + // The menu is never shown, so don't await the returned promise. + openContextMenuInSidebar("a"); + } while (await extension.awaitMessage("continue_test")); + + await extension.unload(); +}); From a69fd162a29cae78af3b3c2be1201dcf1c6a5cba Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 27 Sep 2018 08:21:06 +0000 Subject: [PATCH 19/25] Bug 1280347 - Normalize local ExtensionErrors r=mixedpuppy Normalize errors thrown by extension API implementations in content processes to ensure that extension code can read the error message if the error is an instance of ExtensionUtils.ExtensionError. This code path is triggered in browser_ext_menus_replace.js and browser_ext_menus_replace_menu_permissions.js. Depends on D6625 Differential Revision: https://phabricator.services.mozilla.com/D6626 --HG-- extra : moz-landing-system : lando --- toolkit/components/extensions/ExtensionCommon.jsm | 12 ++++++++++-- toolkit/components/extensions/child/ext-test.js | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/toolkit/components/extensions/ExtensionCommon.jsm b/toolkit/components/extensions/ExtensionCommon.jsm index 560c93c61a21..745fb9a55a02 100644 --- a/toolkit/components/extensions/ExtensionCommon.jsm +++ b/toolkit/components/extensions/ExtensionCommon.jsm @@ -939,11 +939,19 @@ class LocalAPIImplementation extends SchemaAPIInterface { } callFunction(args) { - return this.pathObj[this.name](...args); + try { + return this.pathObj[this.name](...args); + } catch (e) { + throw this.context.normalizeError(e); + } } callFunctionNoReturn(args) { - this.pathObj[this.name](...args); + try { + this.pathObj[this.name](...args); + } catch (e) { + throw this.context.normalizeError(e); + } } callAsyncFunction(args, callback, requireUserInput) { diff --git a/toolkit/components/extensions/child/ext-test.js b/toolkit/components/extensions/child/ext-test.js index daf2ba67a9df..b29fa89a9e4e 100644 --- a/toolkit/components/extensions/child/ext-test.js +++ b/toolkit/components/extensions/child/ext-test.js @@ -19,6 +19,11 @@ * True if the error matches the expected error. */ const errorMatches = (error, expectedError, context) => { + if (typeof error === "object" && error !== null && + !context.principal.subsumes(Cu.getObjectPrincipal(error))) { + Cu.reportError("Error object belongs to the wrong scope."); + return false; + } if (expectedError === null) { return true; } From fd1f17a6e37acc1141f94aed388ca1430432e618 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 27 Sep 2018 08:21:07 +0000 Subject: [PATCH 20/25] Bug 1280347 - Require a permission for the API r=mixedpuppy The new permission is added to make it easier to audit the usage of the API. It is an optional permission, in case we ever decide to introduce a permission warning for it. Differential Revision: https://phabricator.services.mozilla.com/D6771 --HG-- extra : moz-landing-system : lando --- browser/components/extensions/schemas/menus.json | 9 +++++++++ .../test/browser/browser_ext_menus_replace_menu.js | 4 ++-- .../browser/browser_ext_menus_replace_menu_context.js | 2 +- .../browser_ext_menus_replace_menu_permissions.js | 11 ++++++++++- .../extensions/test/xpcshell/test_ext_permissions.js | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/browser/components/extensions/schemas/menus.json b/browser/components/extensions/schemas/menus.json index 2fbafd797ce6..f0434e900cc8 100644 --- a/browser/components/extensions/schemas/menus.json +++ b/browser/components/extensions/schemas/menus.json @@ -15,6 +15,14 @@ "contextMenus" ] }] + }, { + "$extend": "OptionalPermission", + "choices": [{ + "type": "string", + "enum": [ + "menus.overrideContext" + ] + }] } ] }, @@ -404,6 +412,7 @@ }, { "name": "overrideContext", + "permissions": ["menus.overrideContext"], "type": "function", "description": "Show the matching menu items from this extension instead of the default menu. This should be called during a 'contextmenu' DOM event handler, and only applies to the menu that opens after this event.", "parameters": [ diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js index c30f16c9ddfc..2350389d15b7 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js @@ -57,7 +57,7 @@ add_task(async function overrideContext_in_extension_tab() { let extension = ExtensionTestUtils.loadExtension({ manifest: { - permissions: ["menus"], + permissions: ["menus", "menus.overrideContext"], }, files: { "tab.html": ` @@ -250,7 +250,7 @@ add_task(async function overrideContext_sidebar_edge_cases() { let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "temporary", // To automatically show sidebar on load. manifest: { - permissions: ["menus"], + permissions: ["menus", "menus.overrideContext"], sidebar_action: { default_panel: "sidebar.html", }, diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js index af86234b7127..220ad622629d 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js @@ -66,7 +66,7 @@ add_task(async function overrideContext_with_context() { let extension = ExtensionTestUtils.loadExtension({ manifest: { applications: {gecko: {id: "@menu-test-extension"}}, - permissions: ["menus", "tabs", "bookmarks", ""], + permissions: ["menus", "menus.overrideContext", "tabs", "bookmarks", ""], }, files: { "tab.html": ` diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js index 4933c906360d..cbeb94d35f6f 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js @@ -53,6 +53,11 @@ add_task(async function overrideContext_permissions() { // "contextmenu" event. So we use a generator to run tests, and yield // before we call overrideContext after an asynchronous operation. let testGenerator = (async function* () { + browser.test.assertEq(undefined, browser.menus.overrideContext, + "menus.overrideContext requires the 'menus.overrideContext' permission"); + await requestPermissions({permissions: ["menus.overrideContext"]}); + yield; + // context without required property. browser.test.assertThrows( () => { browser.menus.overrideContext({context: "tab"}); }, @@ -114,6 +119,10 @@ add_task(async function overrideContext_permissions() { // Host permissions were revoked, access should be blocked again. assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); + + await browser.permissions.remove({permissions: ["menus.overrideContext"]}); + browser.test.assertEq(undefined, browser.menus.overrideContext, + "menus.overrideContext is unavailable after revoking the permission"); })(); // eslint-disable-next-line mozilla/balanced-listeners @@ -133,7 +142,7 @@ add_task(async function overrideContext_permissions() { useAddonManager: "temporary", // To automatically show sidebar on load. manifest: { permissions: ["menus"], - optional_permissions: ["tabs", "bookmarks", ""], + optional_permissions: ["menus.overrideContext", "tabs", "bookmarks", ""], sidebar_action: { default_panel: "sidebar.html", }, diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js index 00c26288ebbb..ffcfefe3109f 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js @@ -376,6 +376,7 @@ const GRANTED_WITHOUT_USER_PROMPT = [ "identity", "idle", "menus", + "menus.overrideContext", "mozillaAddons", "search", "storage", From e1571e4a5a3993d11c3837b99e0b7939209b5170 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 27 Sep 2018 15:32:14 +0000 Subject: [PATCH 21/25] Bug 1280347 - Remove requirement for the "tab" context r=mixedpuppy See https://bugzilla.mozilla.org/show_bug.cgi?id=1280347#c28 Differential Revision: https://phabricator.services.mozilla.com/D6831 --HG-- extra : moz-landing-system : lando --- .../components/extensions/child/ext-menus.js | 5 +- .../components/extensions/parent/ext-menus.js | 6 +- .../components/extensions/schemas/menus.json | 2 +- .../browser_ext_menus_replace_menu_context.js | 61 +++++++++++++++---- ...wser_ext_menus_replace_menu_permissions.js | 26 ++------ 5 files changed, 61 insertions(+), 39 deletions(-) diff --git a/browser/components/extensions/child/ext-menus.js b/browser/components/extensions/child/ext-menus.js index 8790a75abe45..d8c7eec1c168 100644 --- a/browser/components/extensions/child/ext-menus.js +++ b/browser/components/extensions/child/ext-menus.js @@ -195,9 +195,8 @@ this.menusInternal = class extends ExtensionAPI { return true; }; if (checkValidArg("tab", "tabId")) { - if (!context.extension.hasPermission("tabs") || - !context.extension.whiteListedHosts.subsumes(new MatchPattern(""))) { - throw new ExtensionError(`The "tab" context requires the "tabs" and "" permission.`); + if (!context.extension.hasPermission("tabs")) { + throw new ExtensionError(`The "tab" context requires the "tabs" permission.`); } } if (checkValidArg("bookmark", "bookmarkId")) { diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index 27a473a758d9..d00d912581d6 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -348,7 +348,11 @@ var gMenuBuilder = { item.checked = true; } - if (contextData.tab) { + let {webExtContextData} = contextData; + if (contextData.tab && + // If the menu context was overridden by the extension, do not grant + // activeTab since the extension also controls the tabId. + (!webExtContextData || webExtContextData.extensionId !== item.extension.id)) { item.tabManager.addActiveTabPermission(contextData.tab); } diff --git a/browser/components/extensions/schemas/menus.json b/browser/components/extensions/schemas/menus.json index f0434e900cc8..14fe28cad27a 100644 --- a/browser/components/extensions/schemas/menus.json +++ b/browser/components/extensions/schemas/menus.json @@ -442,7 +442,7 @@ "type": "integer", "minimum": 0, "optional": true, - "description": "Required when context is 'tab'. Requires 'tabs' and '' permission." + "description": "Required when context is 'tab'. Requires 'tabs' permission." } } } diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js index 220ad622629d..8244f9ebfb86 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_context.js @@ -24,7 +24,7 @@ add_task(async function overrideContext_with_context() { browser.test.assertEq("testTabAccess", msg, `Expected message in ${browser.runtime.id}`); let tab = await browser.tabs.get(tabId); if (!tab.url) { // tabs or activeTab not active. - browser.test.sendMessage("testTabAccessDone", false); + browser.test.sendMessage("testTabAccessDone", "tab_no_url"); return; } try { @@ -32,10 +32,12 @@ add_task(async function overrideContext_with_context() { code: "document.URL", }); browser.test.assertEq("http://example.com/?SomeTab", url, "Expected successful executeScript"); + browser.test.sendMessage("testTabAccessDone", "executeScript_ok"); + return; } catch (e) { - browser.test.fail(`Failed to execute script at ${tabId} (${tab.url}): ${e}`); + browser.test.assertEq("Missing host permission for the tab", e.message, "Expected error message"); + browser.test.sendMessage("testTabAccessDone", "executeScript_failed"); } - browser.test.sendMessage("testTabAccessDone", true); }); browser.menus.onShown.addListener((info, tab) => { browser.test.sendMessage("onShown", { @@ -66,7 +68,7 @@ add_task(async function overrideContext_with_context() { let extension = ExtensionTestUtils.loadExtension({ manifest: { applications: {gecko: {id: "@menu-test-extension"}}, - permissions: ["menus", "menus.overrideContext", "tabs", "bookmarks", ""], + permissions: ["menus", "menus.overrideContext", "tabs", "bookmarks"], }, files: { "tab.html": ` @@ -85,6 +87,9 @@ add_task(async function overrideContext_with_context() { let testCases = [{ context: "tab", tabId: tab.id, + }, { + context: "tab", + tabId: tab.id, }, { context: "bookmark", bookmarkId: bookmark.id, @@ -145,14 +150,15 @@ add_task(async function overrideContext_with_context() { `${makeWidgetId(otherExtension.id)}-menuitem-_tab_context`, ], "Expected menu items after changing context to tab"); - // Extension should already be able to execute script due to host permissions. extension.sendMessage("testTabAccess", tabId); - ok(await extension.awaitMessage("testTabAccessDone"), - "Extension has access via permissions"); + is(await extension.awaitMessage("testTabAccessDone"), + "executeScript_failed", + "executeScript should fail due to the lack of permissions."); otherExtension.sendMessage("testTabAccess", tabId); - isnot(await otherExtension.awaitMessage("testTabAccessDone"), - "Other extension should not have activeTab permissions yet."); + is(await otherExtension.awaitMessage("testTabAccessDone"), + "tab_no_url", + "Other extension should not have activeTab permissions yet."); // Click on the menu item of the other extension to unlock host permissions. let menuItems = menu.getElementsByAttribute("label", "tab_context"); @@ -165,13 +171,44 @@ add_task(async function overrideContext_with_context() { tabId, }, "Expected onClicked details after changing context to tab"); + extension.sendMessage("testTabAccess", tabId); + is(await extension.awaitMessage("testTabAccessDone"), + "executeScript_failed", + "executeScript of extension that created the menu should still fail."); + otherExtension.sendMessage("testTabAccess", tabId); - ok(await otherExtension.awaitMessage("testTabAccessDone"), + is(await otherExtension.awaitMessage("testTabAccessDone"), + "executeScript_ok", "Other extension should have activeTab permissions."); } { - // Test case 2: context=bookmark + // Test case 2: context=tab, click on menu item of extension.. + let menu = await openContextMenu("a"); + await extension.awaitMessage("oncontextmenu_in_dom"); + + // The previous test has already verified the visible menu items, + // so we skip checking the onShown result and only test clicking. + await extension.awaitMessage("onShown"); + await otherExtension.awaitMessage("onShown"); + let menuItems = menu.getElementsByAttribute("label", "tab_context"); + is(menuItems.length, 2, "There are two menu items with label 'tab_context'"); + await closeExtensionContextMenu(menuItems[0]); + + Assert.deepEqual(await extension.awaitMessage("onClicked"), { + menuItemId: "tab_context", + bookmarkId: undefined, + tabId, + }, "Expected onClicked details after changing context to tab"); + + extension.sendMessage("testTabAccess", tabId); + is(await extension.awaitMessage("testTabAccessDone"), + "executeScript_failed", + "activeTab permission should not be available to the extension that created the menu."); + } + + { + // Test case 3: context=bookmark let menu = await openContextMenu("a"); await extension.awaitMessage("oncontextmenu_in_dom"); for (let ext of [extension, otherExtension]) { @@ -193,7 +230,7 @@ add_task(async function overrideContext_with_context() { } { - // Test case 3: context=tab, invalid tabId. + // Test case 4: context=tab, invalid tabId. let menu = await openContextMenu("a"); await extension.awaitMessage("oncontextmenu_in_dom"); // When an invalid tabId is used, all extension menu logic is skipped and diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js index cbeb94d35f6f..84655f777395 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js @@ -19,7 +19,7 @@ add_task(async function overrideContext_permissions() { const CONTEXT_OPTIONS_TAB = {context: "tab", tabId: 1}; const CONTEXT_OPTIONS_BOOKMARK = {context: "bookmark", bookmarkId: "x"}; - const E_PERM_TAB = /The "tab" context requires the "tabs" and "" permission/; + const E_PERM_TAB = /The "tab" context requires the "tabs" permission/; const E_PERM_BOOKMARK = /The "bookmark" context requires the "bookmarks" permission/; function assertAllowed(contextOptions) { @@ -94,30 +94,12 @@ add_task(async function overrideContext_permissions() { browser.test.log("Active permissions: tabs"); yield; - assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); - // "tabs" permission was granted, but host permissions are required too. - assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); - - await requestPermissions({origins: [""]}); - await browser.permissions.remove({permissions: ["tabs"]}); - browser.test.log("Active permissions: "); - yield; - - // Just host permissions are not sufficient for either context. - assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); - assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); - - await requestPermissions({permissions: ["tabs"]}); - browser.test.log("Active permissions: , tabs"); - yield; - assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK); assertAllowed(CONTEXT_OPTIONS_TAB); - await browser.permissions.remove({origins: [""]}); - browser.test.log("Active permissions: tabs without hosts"); + await browser.permissions.remove({permissions: ["tabs"]}); + browser.test.log("Active permissions: none"); yield; - // Host permissions were revoked, access should be blocked again. assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB); await browser.permissions.remove({permissions: ["menus.overrideContext"]}); @@ -142,7 +124,7 @@ add_task(async function overrideContext_permissions() { useAddonManager: "temporary", // To automatically show sidebar on load. manifest: { permissions: ["menus"], - optional_permissions: ["menus.overrideContext", "tabs", "bookmarks", ""], + optional_permissions: ["menus.overrideContext", "tabs", "bookmarks"], sidebar_action: { default_panel: "sidebar.html", }, From 4bbd4a30cf4425d74b650afc77d0c4b2bce1f392 Mon Sep 17 00:00:00 2001 From: "Kearwood \"Kip\" Gilbert" Date: Thu, 27 Sep 2018 21:56:20 +0000 Subject: [PATCH 22/25] Bug 1473398 - Add telemetry support to gfxVRExternal and OpenVRSession r=daoshengmu Differential Revision: https://phabricator.services.mozilla.com/D7014 --HG-- extra : moz-landing-system : lando --- gfx/vr/external_api/moz_external_vr.h | 14 +++++++++- gfx/vr/gfxVRExternal.cpp | 39 ++++++++++++++++++--------- gfx/vr/service/OpenVRSession.cpp | 11 ++++++++ gfx/vr/service/OpenVRSession.h | 1 + 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/gfx/vr/external_api/moz_external_vr.h b/gfx/vr/external_api/moz_external_vr.h index 446a3c9391a4..f4739e2653ea 100644 --- a/gfx/vr/external_api/moz_external_vr.h +++ b/gfx/vr/external_api/moz_external_vr.h @@ -7,6 +7,12 @@ #ifndef GFX_VR_EXTERNAL_API_H #define GFX_VR_EXTERNAL_API_H +#define GFX_VR_EIGHTCC(c1, c2, c3, c4, c5, c6, c7, c8) \ + ((uint64_t)(c1) << 56 | (uint64_t)(c2) << 48 | \ + (uint64_t)(c3) << 40 | (uint64_t)(c4) << 32 | \ + (uint64_t)(c5) << 24 | (uint64_t)(c6) << 16 | \ + (uint64_t)(c7) << 8 | (uint64_t)(c8)) + #include #include #include @@ -29,7 +35,7 @@ namespace dom { #endif // MOZILLA_INTERNAL_API namespace gfx { -static const int32_t kVRExternalVersion = 3; +static const int32_t kVRExternalVersion = 4; // We assign VR presentations to groups with a bitmask. // Currently, we will only display either content or chrome. @@ -265,6 +271,9 @@ struct VRDisplayState bool shutdown; #endif // defined(__ANDROID__) char mDisplayName[kVRDisplayNameMaxLen]; + // eight byte character code identifier + // LSB first, so "ABCDEFGH" -> ('H'<<56) + ('G'<<48) + ('F'<<40) + ('E'<<32) + ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). + uint64_t mEightCC; VRDisplayCapabilityFlags mCapabilityFlags; VRFieldOfView mEyeFOV[VRDisplayState::NumEyes]; Point3D_POD mEyeTranslation[VRDisplayState::NumEyes]; @@ -278,6 +287,9 @@ struct VRDisplayState uint64_t mLastSubmittedFrameId; bool mLastSubmittedFrameSuccessful; uint32_t mPresentingGeneration; + // Telemetry + bool mReportsDroppedFrames; + uint64_t mDroppedFrameCount; }; struct VRControllerState diff --git a/gfx/vr/gfxVRExternal.cpp b/gfx/vr/gfxVRExternal.cpp index 00dd3ee37abd..cfdf55722236 100644 --- a/gfx/vr/gfxVRExternal.cpp +++ b/gfx/vr/gfxVRExternal.cpp @@ -133,7 +133,9 @@ VRDisplayExternal::StartPresentation() PushState(); mDisplayInfo.mDisplayState.mLastSubmittedFrameId = 0; - // mTelemetry.mLastDroppedFrameCount = stats.m_nNumReprojectedFrames; + if (mDisplayInfo.mDisplayState.mReportsDroppedFrames) { + mTelemetry.mLastDroppedFrameCount = mDisplayInfo.mDisplayState.mDroppedFrameCount; + } } void @@ -149,20 +151,31 @@ VRDisplayExternal::StopPresentation() PushState(true); - // TODO - Implement telemetry: + Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount; + Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount; + int viewIn = 0; -/* - const TimeDuration duration = TimeStamp::Now() - mTelemetry.mPresentationStart; - Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 2); - Telemetry::Accumulate(Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR, - duration.ToMilliseconds()); + if (mDisplayInfo.mDisplayState.mEightCC == GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) { + // Oculus Desktop API + timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS; + droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS; + viewIn = 1; + } else if (mDisplayInfo.mDisplayState.mEightCC == GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) { + // OpenVR API + timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR; + droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR; + viewIn = 2; + } - ::vr::Compositor_CumulativeStats stats; - mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats)); - const uint32_t droppedFramesPerSec = (stats.m_nNumReprojectedFrames - - mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds(); - Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR, droppedFramesPerSec); -*/ + if (viewIn) { + const TimeDuration duration = TimeStamp::Now() - mTelemetry.mPresentationStart; + Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn); + Telemetry::Accumulate(timeSpentID, + duration.ToMilliseconds()); + const uint32_t droppedFramesPerSec = (mDisplayInfo.mDisplayState.mDroppedFrameCount - + mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds(); + Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec); + } } void diff --git a/gfx/vr/service/OpenVRSession.cpp b/gfx/vr/service/OpenVRSession.cpp index 90cad5fe3bc4..45fd0226b422 100644 --- a/gfx/vr/service/OpenVRSession.cpp +++ b/gfx/vr/service/OpenVRSession.cpp @@ -211,6 +211,7 @@ OpenVRSession::InitState(VRSystemState& aSystemState) { VRDisplayState& state = aSystemState.displayState; strncpy(state.mDisplayName, "OpenVR HMD", kVRDisplayNameMaxLen); + state.mEightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' '); state.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd); state.mIsMounted = false; state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None | @@ -219,6 +220,7 @@ OpenVRSession::InitState(VRSystemState& aSystemState) (int)VRDisplayCapabilityFlags::Cap_External | (int)VRDisplayCapabilityFlags::Cap_Present | (int)VRDisplayCapabilityFlags::Cap_StageParameters); + state.mReportsDroppedFrames = true; ::vr::ETrackedPropertyError err; bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err); @@ -776,6 +778,7 @@ OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) EnumerateControllers(aSystemState); UpdateControllerButtons(aSystemState); UpdateControllerPoses(aSystemState); + UpdateTelemetry(aSystemState); } bool @@ -1093,5 +1096,13 @@ OpenVRSession::StopAllHaptics() } } +void +OpenVRSession::UpdateTelemetry(VRSystemState& aSystemState) +{ + ::vr::Compositor_CumulativeStats stats; + mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats)); + aSystemState.displayState.mDroppedFrameCount = stats.m_nNumReprojectedFrames; +} + } // namespace mozilla } // namespace gfx diff --git a/gfx/vr/service/OpenVRSession.h b/gfx/vr/service/OpenVRSession.h index 48e44ba50fb7..63b210326ce0 100644 --- a/gfx/vr/service/OpenVRSession.h +++ b/gfx/vr/service/OpenVRSession.h @@ -65,6 +65,7 @@ private: void EnumerateControllers(VRSystemState& aState); void UpdateControllerPoses(VRSystemState& aState); void UpdateControllerButtons(VRSystemState& aState); + void UpdateTelemetry(VRSystemState& aSystemState); bool SubmitFrame(void* aTextureHandle, ::vr::ETextureType aTextureType, From 8449b1c4890bda1ed527f7fbb068e14ef7735769 Mon Sep 17 00:00:00 2001 From: Bogdan Tara Date: Fri, 28 Sep 2018 02:42:20 +0300 Subject: [PATCH 23/25] Backed out changeset ba1fef7b14eb (bug 1493955) for GTest failures CLOSED TREE --- modules/libpref/Preferences.cpp | 9 +++----- modules/libpref/Preferences.h | 5 ++--- modules/libpref/test/gtest/Basics.cpp | 21 ------------------- .../antitracking/AntiTrackingCommon.cpp | 1 - .../extensions/ExtensionPolicyService.cpp | 1 - .../tests/gtest/TestCombinedStacks.cpp | 2 -- toolkit/recordreplay/ipc/ChildIPC.cpp | 1 - xpcom/threads/Scheduler.cpp | 1 + xpcom/threads/Scheduler.h | 1 - 9 files changed, 6 insertions(+), 36 deletions(-) diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index 319580ca3ed6..c211fba48110 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -2394,7 +2394,6 @@ nsPrefBranch::GetFloatPref(const char* aPrefName, float* aRetVal) nsAutoCString stringVal; nsresult rv = GetCharPref(aPrefName, stringVal); if (NS_SUCCEEDED(rv)) { - // ToFloat() does a locale-independent conversion. *aRetVal = stringVal.ToFloat(&rv); } @@ -5036,7 +5035,6 @@ Preferences::GetFloat(const char* aPrefName, nsAutoCString result; nsresult rv = Preferences::GetCString(aPrefName, result, aKind); if (NS_SUCCEEDED(rv)) { - // ToFloat() does a locale-independent conversion. *aResult = result.ToFloat(&rv); } return rv; @@ -5847,9 +5845,7 @@ static void SetPref_float(const char* aName, float aDefaultValue) { PrefValue value; - // Convert the value in a locale-independent way. - nsAutoCString defaultValue; - defaultValue.AppendFloat(aDefaultValue); + nsPrintfCString defaultValue("%f", aDefaultValue); value.mStringVal = defaultValue.get(); pref_SetPref(aName, PrefType::String, @@ -5977,7 +5973,8 @@ InitVarCachePref(const nsACString& aName, } } -static void +// XXX: this will eventually become used +MOZ_MAYBE_UNUSED static void InitVarCachePref(const nsACString& aName, float* aCache, float aDefaultValue, diff --git a/modules/libpref/Preferences.h b/modules/libpref/Preferences.h index a11e9ee46a74..1e31b86c5363 100644 --- a/modules/libpref/Preferences.h +++ b/modules/libpref/Preferences.h @@ -19,6 +19,7 @@ #include "nsIObserver.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" +#include "nsPrintfCString.h" #include "nsString.h" #include "nsTArray.h" #include "nsWeakReference.h" @@ -308,9 +309,7 @@ public: float aValue, PrefValueKind aKind = PrefValueKind::User) { - nsAutoCString value; - value.AppendFloat(aValue); - return SetCString(aPrefName, value, aKind); + return SetCString(aPrefName, nsPrintfCString("%f", aValue), aKind); } static nsresult SetCString(const char* aPrefName, diff --git a/modules/libpref/test/gtest/Basics.cpp b/modules/libpref/test/gtest/Basics.cpp index bb14b0ff6843..28291a619b66 100644 --- a/modules/libpref/test/gtest/Basics.cpp +++ b/modules/libpref/test/gtest/Basics.cpp @@ -4,8 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include - #include "gtest/gtest.h" #include "mozilla/Preferences.h" @@ -36,22 +34,3 @@ TEST(PrefsBasics, Errors) ASSERT_FLOAT_EQ(Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::User), 4.44f); } - -TEST(PrefsBasics, FloatConversions) -{ - // Set a global locale that uses the comma as the decimal separator. Since - // we can't tell which locales will be available on a machine the tests are - // executed only if the locale was set correctly. - const char* oldLocale = setlocale(LC_NUMERIC, "nl_NL"); - if (oldLocale != nullptr) { - Preferences::SetFloat("foo.float", 3.33f, PrefValueKind::Default); - Preferences::SetFloat("foo.float", 4.44f, PrefValueKind::User); - ASSERT_FLOAT_EQ( - Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::Default), 3.33f); - ASSERT_FLOAT_EQ( - Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::User), 4.44f); - - // Restore the original locale - setlocale(LC_NUMERIC, oldLocale); - } -} diff --git a/toolkit/components/antitracking/AntiTrackingCommon.cpp b/toolkit/components/antitracking/AntiTrackingCommon.cpp index 0b07c3000258..45d446f12101 100644 --- a/toolkit/components/antitracking/AntiTrackingCommon.cpp +++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp @@ -30,7 +30,6 @@ #include "nsIWebProgressListener.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" -#include "nsPrintfCString.h" #include "nsScriptSecurityManager.h" #include "nsSandboxFlags.h" #include "prtime.h" diff --git a/toolkit/components/extensions/ExtensionPolicyService.cpp b/toolkit/components/extensions/ExtensionPolicyService.cpp index 288a3f666917..93a406408394 100644 --- a/toolkit/components/extensions/ExtensionPolicyService.cpp +++ b/toolkit/components/extensions/ExtensionPolicyService.cpp @@ -29,7 +29,6 @@ #include "nsILoadInfo.h" #include "nsIXULRuntime.h" #include "nsNetUtil.h" -#include "nsPrintfCString.h" #include "nsPIDOMWindow.h" #include "nsXULAppAPI.h" #include "nsQueryObject.h" diff --git a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp index d736b9f5685c..fa75ea5ace43 100644 --- a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp +++ b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp @@ -4,8 +4,6 @@ #include "other/CombinedStacks.h" #include "other/ProcessedStack.h" -#include "nsPrintfCString.h" - using namespace mozilla::Telemetry; using namespace TelemetryTestHelpers; diff --git a/toolkit/recordreplay/ipc/ChildIPC.cpp b/toolkit/recordreplay/ipc/ChildIPC.cpp index 6dcb40258032..ce2e6f58fc01 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.cpp +++ b/toolkit/recordreplay/ipc/ChildIPC.cpp @@ -23,7 +23,6 @@ #include "InfallibleVector.h" #include "MemorySnapshot.h" -#include "nsPrintfCString.h" #include "ParentInternal.h" #include "ProcessRecordReplay.h" #include "ProcessRedirect.h" diff --git a/xpcom/threads/Scheduler.cpp b/xpcom/threads/Scheduler.cpp index c80b85516412..d75d0dd0816d 100644 --- a/xpcom/threads/Scheduler.cpp +++ b/xpcom/threads/Scheduler.cpp @@ -16,6 +16,7 @@ #include "mozilla/SchedulerGroup.h" #include "nsCycleCollector.h" #include "nsIThread.h" +#include "nsPrintfCString.h" #include "nsThread.h" #include "nsThreadManager.h" #include "PrioritizedEventQueue.h" diff --git a/xpcom/threads/Scheduler.h b/xpcom/threads/Scheduler.h index 2c3401a90850..241bd61d359e 100644 --- a/xpcom/threads/Scheduler.h +++ b/xpcom/threads/Scheduler.h @@ -14,7 +14,6 @@ #include "mozilla/UniquePtr.h" #include "nsTArray.h" #include "nsILabelableRunnable.h" -#include "nsPrintfCString.h" // Windows silliness. winbase.h defines an empty no-argument Yield macro. #undef Yield From eaaac5ca035432d52f93114f71bb4950c38e02b9 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Thu, 27 Sep 2018 23:09:55 +0000 Subject: [PATCH 24/25] Bug 1460982 - Convert to a Custom Element;r=adw Differential Revision: https://phabricator.services.mozilla.com/D5912 --HG-- rename : browser/components/search/content/search.xml => browser/components/search/content/searchbar.js extra : moz-landing-system : lando --- browser/base/content/browser.css | 4 - browser/base/content/global-scripts.inc | 1 + .../browser/browser_policy_search_engine.js | 5 +- browser/components/search/content/search.xml | 491 +----------------- .../components/search/content/searchbar.js | 482 +++++++++++++++++ browser/components/search/jar.mn | 1 + .../components/search/test/browser_426329.js | 5 +- .../test/browser_hiddenOneOffs_diacritics.js | 4 +- .../search/test/browser_oneOffContextMenu.js | 4 +- .../browser_oneOffContextMenu_setDefault.js | 4 +- .../search/test/browser_oneOffHeader.js | 10 +- .../browser_searchbar_keyboard_navigation.js | 2 +- .../test/browser_searchbar_openpopup.js | 18 +- ...earchbar_smallpanel_keyboard_navigation.js | 6 +- browser/components/uitour/UITour.jsm | 4 +- .../browser_ext_themes_toolbar_fields.js | 2 +- toolkit/content/customElements.js | 4 +- toolkit/content/widgets/editor.js | 2 + toolkit/content/widgets/findbar.js | 3 +- toolkit/content/widgets/general.js | 2 + toolkit/content/widgets/stringbundle.js | 4 +- toolkit/content/widgets/tabbox.js | 2 + toolkit/content/widgets/textbox.js | 2 + .../lib/environments/browser-window.js | 1 + 24 files changed, 530 insertions(+), 533 deletions(-) create mode 100644 browser/components/search/content/searchbar.js diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index aa572f16cf68..8cf0da126783 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -47,10 +47,6 @@ min-width: -moz-fit-content; } -searchbar { - -moz-binding: url("chrome://browser/content/search/search.xml#searchbar"); -} - .searchbar-textbox { -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox"); } diff --git a/browser/base/content/global-scripts.inc b/browser/base/content/global-scripts.inc index bea81d7d8358..ec8fa4607a48 100644 --- a/browser/base/content/global-scripts.inc +++ b/browser/base/content/global-scripts.inc @@ -16,6 +16,7 @@ Components.utils.import("resource://gre/modules/Services.jsm"); for (let script of [ "chrome://browser/content/browser.js", + "chrome://browser/content/search/searchbar.js", "chrome://browser/content/browser-captivePortal.js", "chrome://browser/content/browser-compacttheme.js", diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js index 1594bb51bec2..92a416d9aaac 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js @@ -26,9 +26,8 @@ async function test_opensearch(shouldWork) { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, rootDir + "opensearch.html"); let searchPopup = document.getElementById("PopupSearchAutoComplete"); let promiseSearchPopupShown = BrowserTestUtils.waitForEvent(searchPopup, "popupshown"); - let searchBarButton = document.getAnonymousElementByAttribute(searchBar, - "anonid", - "searchbar-search-button"); + let searchBarButton = searchBar.querySelector(".searchbar-search-button"); + searchBarButton.click(); await promiseSearchPopupShown; let oneOffsContainer = document.getAnonymousElementByAttribute(searchPopup, diff --git a/browser/components/search/content/search.xml b/browser/components/search/content/search.xml index 4e0aa3a07e27..ffad42815b58 100644 --- a/browser/components/search/content/search.xml +++ b/browser/components/search/content/search.xml @@ -16,486 +16,11 @@ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> - - - - - - - - - - - - - - - - - - - { - window.requestIdleCallback(() => { - Services.search.init(aStatus => { - // Bail out if the binding's been destroyed - if (!this._initialized) - return; - - if (Components.isSuccessCode(aStatus)) { - // Refresh the display (updating icon, etc) - this.updateDisplay(); - BrowserSearch.updateOpenSearchBadge(); - } else { - Cu.reportError("Cannot initialize search service, bailing out: " + aStatus); - } - }); - }); - }); - - // Wait until the popupshowing event to avoid forcing immediate - // attachment of the search-one-offs binding. - this.textbox.popup.addEventListener("popupshowing", () => { - let oneOffButtons = this.textbox.popup.oneOffButtons; - // Some accessibility tests create their own that doesn't - // use the popup binding below, so null-check oneOffButtons. - if (oneOffButtons) { - oneOffButtons.telemetryOrigin = "searchbar"; - // Set .textbox first, since the popup setter will cause - // a _rebuild call that uses it. - oneOffButtons.textbox = this.textbox; - oneOffButtons.popup = this.textbox.popup; - } - }, {capture: true, once: true}); - ]]> - - - - - - - - false - false - document.getAnonymousElementByAttribute(this, - "anonid", "searchbar-stringbundle"); - false - document.getAnonymousElementByAttribute(this, - "anonid", "searchbar-textbox"); - null - - (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - = 0 && newIndex < this.engines.length) { - this.currentEngine = this.engines[newIndex]; - } - - aEvent.preventDefault(); - aEvent.stopPropagation(); - - this.openSuggestionsPanel(); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this.currentEngine = engine, - }; - Services.search.addEngine(target.getAttribute("uri"), null, - target.getAttribute("src"), false, - installCallback); - } else - return; - - this.focus(); - this.select(); - ]]> - - - - - - - - - - - - - - - - - - - - - @@ -804,11 +329,11 @@ + action='this.closest("searchbar").selectEngine(event, false);'/> + action='this.closest("searchbar").selectEngine(event, true);'/> diff --git a/browser/components/search/content/searchbar.js b/browser/components/search/content/searchbar.js new file mode 100644 index 000000000000..3774f4cbeefd --- /dev/null +++ b/browser/components/search/content/searchbar.js @@ -0,0 +1,482 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* eslint-env mozilla/browser-window */ +/* globals XULCommandEvent */ + +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. +{ + +const inheritsMap = { + ".searchbar-textbox": ["disabled", "disableautocomplete", "searchengine", "src", "newlines"], + ".searchbar-search-button": ["addengines"], +}; + +function inheritAttribute(parent, child, attr) { + if (!parent.hasAttribute(attr)) { + child.removeAttribute(attr); + } else { + child.setAttribute(attr, parent.getAttribute(attr)); + } +} + +class MozSearchbar extends MozXULElement { + + static get observedAttributes() { + let unique = new Set(); + for (var i in inheritsMap) { + inheritsMap[i].forEach(attr => unique.add(attr)); + } + return Array.from(unique); + } + + attributeChangedCallback() { + this.inheritAttributes(); + } + + inheritAttributes() { + if (!this.isConnected) { + return; + } + + for (let sel in inheritsMap) { + let node = this.querySelector(sel); + for (let attr of inheritsMap[sel]) { + inheritAttribute(this, node, attr); + } + } + } + + constructor() { + super(); + this.destroy = this.destroy.bind(this); + this._setupEventListeners(); + let searchbar = this; + this.observer = { + observe(aEngine, aTopic, aVerb) { + if (aTopic == "browser-search-engine-modified" || + (aTopic == "browser-search-service" && aVerb == "init-complete")) { + // Make sure the engine list is refetched next time it's needed + searchbar._engines = null; + + // Update the popup header and update the display after any modification. + searchbar._textbox.popup.updateHeader(); + searchbar.updateDisplay(); + } + }, + QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]), + }; + this.content = MozXULElement.parseXULToFragment(` + + + + + + + + + + + + + `, ["chrome://browser/locale/browser.dtd"]); + } + + connectedCallback() { + // Don't initialize if this isn't going to be visible + if (this.closest("#BrowserToolbarPalette")) { + return; + } + + this.appendChild(document.importNode(this.content, true)); + this.inheritAttributes(); + window.addEventListener("unload", this.destroy); + this._ignoreFocus = false; + + this._clickClosedPopup = false; + + this._stringBundle = this.querySelector("stringbundle"); + + this._textboxInitialized = false; + + this._textbox = this.querySelector(".searchbar-textbox"); + + this._engines = null; + + this.FormHistory = (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; + + if (this.parentNode.parentNode.localName == "toolbarpaletteitem") + return; + + Services.obs.addObserver(this.observer, "browser-search-engine-modified"); + Services.obs.addObserver(this.observer, "browser-search-service"); + + this._initialized = true; + + (window.delayedStartupPromise || Promise.resolve()).then(() => { + window.requestIdleCallback(() => { + Services.search.init(aStatus => { + // Bail out if the binding's been destroyed + if (!this._initialized) + return; + + if (Components.isSuccessCode(aStatus)) { + // Refresh the display (updating icon, etc) + this.updateDisplay(); + BrowserSearch.updateOpenSearchBadge(); + } else { + Cu.reportError("Cannot initialize search service, bailing out: " + aStatus); + } + }); + }); + }); + + // Wait until the popupshowing event to avoid forcing immediate + // attachment of the search-one-offs binding. + this.textbox.popup.addEventListener("popupshowing", () => { + let oneOffButtons = this.textbox.popup.oneOffButtons; + // Some accessibility tests create their own that doesn't + // use the popup binding below, so null-check oneOffButtons. + if (oneOffButtons) { + oneOffButtons.telemetryOrigin = "searchbar"; + // Set .textbox first, since the popup setter will cause + // a _rebuild call that uses it. + oneOffButtons.textbox = this.textbox; + oneOffButtons.popup = this.textbox.popup; + } + }, { capture: true, once: true }); + } + + get engines() { + if (!this._engines) + this._engines = Services.search.getVisibleEngines(); + return this._engines; + } + + set currentEngine(val) { + Services.search.currentEngine = val; + return val; + } + + get currentEngine() { + var currentEngine = Services.search.currentEngine; + // Return a dummy engine if there is no currentEngine + return currentEngine || { name: "", uri: null }; + } + /** + * textbox is used by sanitize.js to clear the undo history when + * clearing form information. + */ + get textbox() { + return this._textbox; + } + + set value(val) { + return this._textbox.value = val; + } + + get value() { + return this._textbox.value; + } + + destroy() { + if (this._initialized) { + this._initialized = false; + window.removeEventListener("unload", this.destroy); + + Services.obs.removeObserver(this.observer, "browser-search-engine-modified"); + Services.obs.removeObserver(this.observer, "browser-search-service"); + } + + // Make sure to break the cycle from _textbox to us. Otherwise we leak + // the world. But make sure it's actually pointing to us. + // Also make sure the textbox has ever been constructed, otherwise the + // _textbox getter will cause the textbox constructor to run, add an + // observer, and leak the world too. + if (this._textboxInitialized && this._textbox.mController.input == this) + this._textbox.mController.input = null; + } + + focus() { + this._textbox.focus(); + } + + select() { + this._textbox.select(); + } + + setIcon(element, uri) { + element.setAttribute("src", uri); + } + + updateDisplay() { + var uri = this.currentEngine.iconURI; + this.setIcon(this, uri ? uri.spec : ""); + + var name = this.currentEngine.name; + var text = this._stringBundle.getFormattedString("searchtip", [name]); + this._textbox.label = text; + this._textbox.tooltipText = text; + } + + updateGoButtonVisibility() { + this.querySelector(".search-go-button").hidden = !this._textbox.value; + } + + openSuggestionsPanel(aShowOnlySettingsIfEmpty) { + if (this._textbox.open) + return; + + this._textbox.showHistoryPopup(); + + if (this._textbox.value) { + // showHistoryPopup does a startSearch("") call, ensure the + // controller handles the text from the input box instead: + this._textbox.mController.handleText(); + } else if (aShowOnlySettingsIfEmpty) { + this.setAttribute("showonlysettings", "true"); + } + } + + selectEngine(aEvent, isNextEngine) { + // Find the new index + var newIndex = this.engines.indexOf(this.currentEngine); + newIndex += isNextEngine ? 1 : -1; + + if (newIndex >= 0 && newIndex < this.engines.length) { + this.currentEngine = this.engines[newIndex]; + } + + aEvent.preventDefault(); + aEvent.stopPropagation(); + + this.openSuggestionsPanel(); + } + + handleSearchCommand(aEvent, aEngine, aForceNewTab) { + var where = "current"; + let params; + + // Open ctrl/cmd clicks on one-off buttons in a new background tab. + if (aEvent && aEvent.originalTarget.classList.contains("search-go-button")) { + if (aEvent.button == 2) + return; + where = whereToOpenLink(aEvent, false, true); + } else if (aForceNewTab) { + where = "tab"; + if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) + where += "-background"; + } else { + var newTabPref = Services.prefs.getBoolPref("browser.search.openintab"); + if (((aEvent instanceof KeyboardEvent && aEvent.altKey) ^ newTabPref) && + !isTabEmpty(gBrowser.selectedTab)) { + where = "tab"; + } + if ((aEvent instanceof MouseEvent) && + (aEvent.button == 1 || aEvent.getModifierState("Accel"))) { + where = "tab"; + params = { + inBackground: true, + }; + } + } + + this.handleSearchCommandWhere(aEvent, aEngine, where, params); + } + + handleSearchCommandWhere(aEvent, aEngine, aWhere, aParams) { + var textBox = this._textbox; + var textValue = textBox.value; + + let selection = this.telemetrySearchDetails; + let oneOffRecorded = false; + + BrowserUsageTelemetry.recordSearchbarSelectedResultMethod( + aEvent, + selection ? selection.index : -1 + ); + + if (!selection || (selection.index == -1)) { + oneOffRecorded = this.textbox.popup.oneOffButtons + .maybeRecordTelemetry(aEvent, aWhere, aParams); + if (!oneOffRecorded) { + let source = "unknown"; + let type = "unknown"; + let target = aEvent.originalTarget; + if (aEvent instanceof KeyboardEvent) { + type = "key"; + } else if (aEvent instanceof MouseEvent) { + type = "mouse"; + if (target.classList.contains("search-panel-header") || + target.parentNode.classList.contains("search-panel-header")) { + source = "header"; + } + } else if (aEvent instanceof XULCommandEvent) { + if (target.getAttribute("anonid") == "paste-and-search") { + source = "paste"; + } + } + if (!aEngine) { + aEngine = this.currentEngine; + } + BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type, + aWhere); + } + } + + // This is a one-off search only if oneOffRecorded is true. + this.doSearch(textValue, aWhere, aEngine, aParams, oneOffRecorded); + + if (aWhere == "tab" && aParams && aParams.inBackground) + this.focus(); + } + + doSearch(aData, aWhere, aEngine, aParams, aOneOff) { + var textBox = this._textbox; + + // Save the current value in the form history + if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) { + this.FormHistory.update({ + op: "bump", + fieldname: textBox.getAttribute("autocompletesearchparam"), + value: aData, + }, { + handleError(aError) { + Cu.reportError("Saving search to form history failed: " + aError.message); + }, + }); + } + + let engine = aEngine || this.currentEngine; + var submission = engine.getSubmission(aData, null, "searchbar"); + let telemetrySearchDetails = this.telemetrySearchDetails; + this.telemetrySearchDetails = null; + if (telemetrySearchDetails && telemetrySearchDetails.index == -1) { + telemetrySearchDetails = null; + } + // If we hit here, we come either from a one-off, a plain search or a suggestion. + const details = { + isOneOff: aOneOff, + isSuggestion: (!aOneOff && telemetrySearchDetails), + selection: telemetrySearchDetails, + }; + BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details); + // null parameter below specifies HTML response for search + let params = { + postData: submission.postData, + }; + if (aParams) { + for (let key in aParams) { + params[key] = aParams[key]; + } + } + openTrustedLinkIn(submission.uri.spec, aWhere, params); + } + + disconnectedCallback() { + this.destroy(); + while (this.firstChild) { + this.firstChild.remove(); + } + } + + _setupEventListeners() { + this.addEventListener("command", (event) => { + const target = event.originalTarget; + if (target.engine) { + this.currentEngine = target.engine; + } else if (target.classList.contains("addengine-item")) { + // Select the installed engine if the installation succeeds + var installCallback = { + onSuccess: engine => this.currentEngine = engine, + }; + Services.search.addEngine(target.getAttribute("uri"), null, + target.getAttribute("src"), false, + installCallback); + } else + return; + + this.focus(); + this.select(); + }); + + this.addEventListener("DOMMouseScroll", (event) => { this.selectEngine(event, (event.detail > 0)); }, true); + + this.addEventListener("input", (event) => { this.updateGoButtonVisibility(); }); + + this.addEventListener("drop", (event) => { this.updateGoButtonVisibility(); }); + + this.addEventListener("blur", (event) => { + // If the input field is still focused then a different window has + // received focus, ignore the next focus event. + this._ignoreFocus = (document.activeElement == this._textbox.inputField); + }, true); + + this.addEventListener("focus", (event) => { + // Speculatively connect to the current engine's search URI (and + // suggest URI, if different) to reduce request latency + this.currentEngine.speculativeConnect({ + window, + originAttributes: gBrowser.contentPrincipal + .originAttributes, + }); + + if (this._ignoreFocus) { + // This window has been re-focused, don't show the suggestions + this._ignoreFocus = false; + return; + } + + // Don't open the suggestions if there is no text in the textbox. + if (!this._textbox.value) + return; + + // Don't open the suggestions if the mouse was used to focus the + // textbox, that will be taken care of in the click handler. + if (Services.focus.getLastFocusMethod(window) & Services.focus.FLAG_BYMOUSE) + return; + + this.openSuggestionsPanel(); + }, true); + + this.addEventListener("mousedown", (event) => { + if (event.originalTarget.classList.contains("searchbar-search-button")) { + this._clickClosedPopup = this._textbox.popup._isHiding; + } + }, true); + + this.addEventListener("mousedown", (event) => { + // Ignore right clicks + if (event.button != 0) { + return; + } + + // Ignore clicks on the search go button. + if (event.originalTarget.classList.contains("search-go-button")) { + return; + } + + let isIconClick = event.originalTarget.classList.contains("searchbar-search-button"); + + // Ignore clicks on the icon if they were made to close the popup + if (isIconClick && this._clickClosedPopup) { + return; + } + + // Open the suggestions whenever clicking on the search icon or if there + // is text in the textbox. + if (isIconClick || this._textbox.value) { + this.openSuggestionsPanel(true); + } + }); + + } +} + +customElements.define("searchbar", MozSearchbar); + +} diff --git a/browser/components/search/jar.mn b/browser/components/search/jar.mn index 12e6c6f4eb78..2d8c9b843032 100644 --- a/browser/components/search/jar.mn +++ b/browser/components/search/jar.mn @@ -4,6 +4,7 @@ browser.jar: content/browser/search/search.xml (content/search.xml) + content/browser/search/searchbar.js (content/searchbar.js) content/browser/search/searchReset.xhtml (content/searchReset.xhtml) content/browser/search/searchReset.js (content/searchReset.js) diff --git a/browser/components/search/test/browser_426329.js b/browser/components/search/test/browser_426329.js index 0ae868c8c3c1..aabfab6ebf2c 100644 --- a/browser/components/search/test/browser_426329.js +++ b/browser/components/search/test/browser_426329.js @@ -70,8 +70,7 @@ function promiseSetEngine() { case "engine-current": ok(ss.currentEngine.name == "Bug 426329", "currentEngine set"); searchBar = BrowserSearch.searchBar; - searchButton = document.getAnonymousElementByAttribute(searchBar, - "anonid", "search-go-button"); + searchButton = searchBar.querySelector(".search-go-button"); ok(searchButton, "got search-go-button"); searchBar.value = "test"; @@ -117,7 +116,7 @@ async function prepareTest() { if (document.activeElement == searchBar) return; - let focusPromise = BrowserTestUtils.waitForEvent(searchBar, "focus"); + let focusPromise = BrowserTestUtils.waitForEvent(searchBar.textbox, "focus"); gURLBar.focus(); searchBar.focus(); await focusPromise; diff --git a/browser/components/search/test/browser_hiddenOneOffs_diacritics.js b/browser/components/search/test/browser_hiddenOneOffs_diacritics.js index f181f98ab7f5..5ca124e8cb75 100644 --- a/browser/components/search/test/browser_hiddenOneOffs_diacritics.js +++ b/browser/components/search/test/browser_hiddenOneOffs_diacritics.js @@ -17,9 +17,7 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); + searchIcon = searchbar.querySelector(".searchbar-search-button"); let currentEngine = Services.search.currentEngine; await promiseNewEngine("testEngine_diacritics.xml", {setAsCurrent: false}); diff --git a/browser/components/search/test/browser_oneOffContextMenu.js b/browser/components/search/test/browser_oneOffContextMenu.js index 1046bf52a01d..1d349154ad27 100644 --- a/browser/components/search/test/browser_oneOffContextMenu.js +++ b/browser/components/search/test/browser_oneOffContextMenu.js @@ -25,9 +25,7 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); + searchIcon = searchbar.querySelector(".searchbar-search-button"); await promiseNewEngine(TEST_ENGINE_BASENAME, { setAsCurrent: false, diff --git a/browser/components/search/test/browser_oneOffContextMenu_setDefault.js b/browser/components/search/test/browser_oneOffContextMenu_setDefault.js index 8b2cc143b916..b8a8c26464cb 100644 --- a/browser/components/search/test/browser_oneOffContextMenu_setDefault.js +++ b/browser/components/search/test/browser_oneOffContextMenu_setDefault.js @@ -31,9 +31,7 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); + searchIcon = searchbar.querySelector(".searchbar-search-button"); await promiseNewEngine(TEST_ENGINE_BASENAME, { setAsCurrent: false, diff --git a/browser/components/search/test/browser_oneOffHeader.js b/browser/components/search/test/browser_oneOffHeader.js index 40225c975edf..29c97d9c7c5a 100644 --- a/browser/components/search/test/browser_oneOffHeader.js +++ b/browser/components/search/test/browser_oneOffHeader.js @@ -58,9 +58,7 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); + searchIcon = searchbar.querySelector(".searchbar-search-button"); await promiseNewEngine("testEngine.xml"); }); @@ -97,7 +95,7 @@ add_task(async function test_notext() { }); add_task(async function test_text() { - searchbar._textbox.value = "foo"; + searchbar.textbox.value = "foo"; let promise = promiseEvent(searchPopup, "popupshown"); info("Opening search panel"); @@ -136,7 +134,7 @@ add_task(async function test_text() { }); let url = Services.search.currentEngine - .getSubmission(searchbar._textbox.value).uri.spec; + .getSubmission(searchbar.textbox.value).uri.spec; await promiseTabLoadEvent(gBrowser.selectedTab, url); // Move the cursor out of the panel area to avoid messing with other tests. @@ -144,5 +142,5 @@ add_task(async function test_text() { }); add_task(async function cleanup() { - searchbar._textbox.value = ""; + searchbar.textbox.value = ""; }); diff --git a/browser/components/search/test/browser_searchbar_keyboard_navigation.js b/browser/components/search/test/browser_searchbar_keyboard_navigation.js index b98b4a3b416c..ee46e21ad9d5 100644 --- a/browser/components/search/test/browser_searchbar_keyboard_navigation.js +++ b/browser/components/search/test/browser_searchbar_keyboard_navigation.js @@ -28,7 +28,7 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - textbox = searchbar._textbox; + textbox = searchbar.textbox; await promiseNewEngine("testEngine.xml"); diff --git a/browser/components/search/test/browser_searchbar_openpopup.js b/browser/components/search/test/browser_searchbar_openpopup.js index d42f98bf1067..64aa7d0ed4cf 100644 --- a/browser/components/search/test/browser_searchbar_openpopup.js +++ b/browser/components/search/test/browser_searchbar_openpopup.js @@ -63,13 +63,9 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - textbox = searchbar._textbox; - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); - goButton = document.getAnonymousElementByAttribute( - searchbar, "anonid", "search-go-button" - ); + textbox = searchbar.textbox; + searchIcon = searchbar.querySelector(".searchbar-search-button"); + goButton = searchbar.querySelector(".search-go-button"); await promiseNewEngine("testEngine.xml"); @@ -231,7 +227,7 @@ add_task(async function focus_change_closes_popup() { is(textbox.selectionEnd, 3, "Should have selected all of the text"); promise = promiseEvent(searchPopup, "popuphidden"); - let promise2 = promiseEvent(searchbar, "blur"); + let promise2 = promiseEvent(searchbar.textbox, "blur"); EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true}); await promise; await promise2; @@ -254,7 +250,7 @@ add_task(async function focus_change_closes_small_popup() { is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar"); promise = promiseEvent(searchPopup, "popuphidden"); - let promise2 = promiseEvent(searchbar, "blur"); + let promise2 = promiseEvent(searchbar.textbox, "blur"); EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true}); await promise; await promise2; @@ -369,7 +365,7 @@ add_task(async function refocus_window_doesnt_open_popup_mouse() { } searchPopup.addEventListener("popupshowing", listener); - promise = promiseEvent(searchbar, "focus"); + promise = promiseEvent(searchbar.textbox, "focus"); newWin.close(); await promise; @@ -406,7 +402,7 @@ add_task(async function refocus_window_doesnt_open_popup_keyboard() { } searchPopup.addEventListener("popupshowing", listener); - promise = promiseEvent(searchbar, "focus"); + promise = promiseEvent(searchbar.textbox, "focus"); newWin.close(); await promise; diff --git a/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js b/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js index 15e170f2f672..befbebf14e1a 100644 --- a/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js +++ b/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js @@ -28,10 +28,8 @@ add_task(async function init() { registerCleanupFunction(() => { gCUITestUtils.removeSearchBar(); }); - textbox = searchbar._textbox; - searchIcon = document.getAnonymousElementByAttribute( - searchbar, "anonid", "searchbar-search-button" - ); + textbox = searchbar.textbox; + searchIcon = searchbar.querySelector(".searchbar-search-button"); await promiseNewEngine("testEngine.xml"); diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index 5cfeca514424..cf605f8789c2 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -153,9 +153,7 @@ var UITour = { ["searchIcon", { query: (aDocument) => { let searchbar = aDocument.getElementById("searchbar"); - return aDocument.getAnonymousElementByAttribute(searchbar, - "anonid", - "searchbar-search-button"); + return searchbar.querySelector(".searchbar-search-button"); }, widgetName: "search-container", }], diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js index 8b957677381a..2fa61c8ab485 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js @@ -51,7 +51,7 @@ add_task(async function test_support_toolbar_field_properties() { let toolbox = document.querySelector("#navigator-toolbox"); let fields = [ toolbox.querySelector("#urlbar"), - document.getAnonymousElementByAttribute(searchbar, "anonid", "searchbar-textbox"), + searchbar.querySelector(".searchbar-textbox"), ].filter(field => { let bounds = field.getBoundingClientRect(); return bounds.width > 0 && bounds.height > 0; diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js index 8749dfeca105..46dd2f21360c 100644 --- a/toolkit/content/customElements.js +++ b/toolkit/content/customElements.js @@ -6,8 +6,8 @@ "use strict"; -// This is loaded into all XUL windows. Wrap in a block to prevent -// leaking to window scope. +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { ChromeUtils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/content/widgets/editor.js b/toolkit/content/widgets/editor.js index d9c0e9d18483..6ccf680083e8 100644 --- a/toolkit/content/widgets/editor.js +++ b/toolkit/content/widgets/editor.js @@ -4,6 +4,8 @@ "use strict"; +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { /* globals XULFrameElement */ diff --git a/toolkit/content/widgets/findbar.js b/toolkit/content/widgets/findbar.js index 03ebe0c3d374..5a51871bc539 100644 --- a/toolkit/content/widgets/findbar.js +++ b/toolkit/content/widgets/findbar.js @@ -5,7 +5,8 @@ "use strict"; -// Wrap to prevent accidentally leaking to window scope: +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { ChromeUtils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/content/widgets/general.js b/toolkit/content/widgets/general.js index 03609a0c0f67..6d05d0500a2b 100644 --- a/toolkit/content/widgets/general.js +++ b/toolkit/content/widgets/general.js @@ -4,6 +4,8 @@ "use strict"; +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { class MozDeck extends MozXULElement { diff --git a/toolkit/content/widgets/stringbundle.js b/toolkit/content/widgets/stringbundle.js index d227cc8ec303..a489a79e7fa2 100644 --- a/toolkit/content/widgets/stringbundle.js +++ b/toolkit/content/widgets/stringbundle.js @@ -4,8 +4,8 @@ "use strict"; -// This is loaded into all XUL windows. Wrap in a block to prevent -// leaking to window scope. +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { ChromeUtils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js index 018b2f90a1d9..e37e13fef999 100644 --- a/toolkit/content/widgets/tabbox.js +++ b/toolkit/content/widgets/tabbox.js @@ -4,6 +4,8 @@ "use strict"; +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { class MozTabbox extends MozXULElement { diff --git a/toolkit/content/widgets/textbox.js b/toolkit/content/widgets/textbox.js index a4d76ebf595d..71142287cf96 100644 --- a/toolkit/content/widgets/textbox.js +++ b/toolkit/content/widgets/textbox.js @@ -4,6 +4,8 @@ "use strict"; +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. { const cachedFragments = { diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js index c7b527cf0883..854412525f8a 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js @@ -72,6 +72,7 @@ function getGlobalScriptsIncludes() { let match = line.match(globalScriptsRegExp); if (match) { let sourceFile = (match[1] || match[2]) + .replace("chrome://browser/content/search/", "browser/components/search/content/") .replace("chrome://browser/content/", "browser/base/content/") .replace("chrome://global/content/", "toolkit/content/"); From 62682de0bae10cc15efc745f8a2d0919d40d9632 Mon Sep 17 00:00:00 2001 From: Coroiu Cristina Date: Fri, 28 Sep 2018 07:29:39 +0300 Subject: [PATCH 25/25] Backed out changeset 39762ef5d56e (bug 1493427) for frequent talos chrome failures a=backout --HG-- rename : browser/base/content/test/siteIdentity/browser_navigation_failures.js => browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js --- .../content/test/siteIdentity/browser.ini | 2 +- .../browser_navigation_failures.js | 41 ------------------- .../browser_tls_handshake_failure.js | 25 +++++++++++ .../manager/ssl/nsSecureBrowserUIImpl.cpp | 24 ++++------- 4 files changed, 34 insertions(+), 58 deletions(-) delete mode 100644 browser/base/content/test/siteIdentity/browser_navigation_failures.js create mode 100644 browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini index 243230ea694f..c0b9c966a27b 100644 --- a/browser/base/content/test/siteIdentity/browser.ini +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -108,4 +108,4 @@ support-files = [browser_iframe_navigation.js] support-files = iframe_navigation.html -[browser_navigation_failures.js] +[browser_tls_handshake_failure.js] diff --git a/browser/base/content/test/siteIdentity/browser_navigation_failures.js b/browser/base/content/test/siteIdentity/browser_navigation_failures.js deleted file mode 100644 index c1ba251d4bcc..000000000000 --- a/browser/base/content/test/siteIdentity/browser_navigation_failures.js +++ /dev/null @@ -1,41 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -// Tests that the site identity indicator is properly updated for navigations -// that fail for various reasons. In particular, we currently test TLS handshake -// failures and about: pages that don't actually exist. -// See bug 1492424 and bug 1493427. - -const kSecureURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content", - "https://example.com") + "dummy_page.html"; -add_task(async function() { - await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => { - let identityMode = window.document.getElementById("identity-box").className; - is(identityMode, "verifiedDomain", "identity should be secure before"); - - const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/"; - // Try to connect to a server where the TLS handshake will fail. - BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI); - await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true); - - let newIdentityMode = window.document.getElementById("identity-box").className; - is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after"); - }); -}); - -add_task(async function() { - await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => { - let identityMode = window.document.getElementById("identity-box").className; - is(identityMode, "verifiedDomain", "identity should be secure before"); - - const BAD_ABOUT_PAGE_URI = "about:somethingthatdoesnotexist"; - // Try to load an about: page that doesn't exist - BrowserTestUtils.loadURI(browser, BAD_ABOUT_PAGE_URI); - await BrowserTestUtils.browserLoaded(browser, false, BAD_ABOUT_PAGE_URI, true); - - let newIdentityMode = window.document.getElementById("identity-box").className; - is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after"); - }); -}); diff --git a/browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js b/browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js new file mode 100644 index 000000000000..359650ac8e3e --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the site identity indicator is properly updated for connections +// where the TLS handshake fails. +// See bug 1492424. + +add_task(async function() { + let rootURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content", + "https://example.com"); + await BrowserTestUtils.withNewTab(rootURI + "dummy_page.html", async (browser) => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "verifiedDomain", "identity should be secure before"); + + const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/"; + // Try to connect to a server where the TLS handshake will fail. + BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI); + await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true); + + let newIdentityMode = window.document.getElementById("identity-box").className; + is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after"); + }); +}); diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.cpp b/security/manager/ssl/nsSecureBrowserUIImpl.cpp index 9b8b088d5eba..a608ec8a928f 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp +++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp @@ -251,8 +251,7 @@ nsSecureBrowserUIImpl::UpdateStateAndSecurityInfo(nsIChannel* channel, // CheckForBlockedContent). // When we receive a notification from the top-level nsIWebProgress, we extract // any relevant security information and set our state accordingly. We then call -// OnSecurityChange on the docShell corresponding to the nsIWebProgress we were -// initialized with to notify any downstream listeners of the security state. +// OnSecurityChange to notify any downstream listeners of the security state. NS_IMETHODIMP nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, @@ -294,22 +293,15 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - } - nsCOMPtr docShell = do_QueryReferent(mDocShell); - if (docShell) { - nsCOMPtr eventSink = do_QueryInterface(docShell); - if (eventSink) { - MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, - (" calling OnSecurityChange %p %x\n", aRequest, mState)); - Unused << eventSink->OnSecurityChange(aRequest, mState); - } else { - MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, - (" docShell doesn't implement nsISecurityEventSink? %p\n", - docShell.get())); + nsCOMPtr eventSink; + NS_QueryNotificationCallbacks(channel, eventSink); + if (NS_WARN_IF(!eventSink)) { + return NS_ERROR_INVALID_ARG; } - } else { - MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" docShell went away?\n")); + MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, + (" calling OnSecurityChange %p %x\n", aRequest, mState)); + Unused << eventSink->OnSecurityChange(aRequest, mState); } return NS_OK;