diff --git a/b2g/chrome/content/devtools/hud.js b/b2g/chrome/content/devtools/hud.js index 537ae66bf9a2..0d0bbb677832 100644 --- a/b2g/chrome/content/devtools/hud.js +++ b/b2g/chrome/content/devtools/hud.js @@ -765,9 +765,25 @@ let performanceEntriesWatcher = { _fronts: new Map(), _appLaunchName: null, _appLaunchStartTime: null, + _supported: [ + 'contentInteractive', + 'navigationInteractive', + 'navigationLoaded', + 'visuallyLoaded', + 'fullyLoaded', + 'mediaEnumerated', + 'scanEnd' + ], init(client) { this._client = client; + let setting = 'devtools.telemetry.supported_performance_marks'; + let defaultValue = this._supported.join(','); + + SettingsListener.observe(setting, defaultValue, supported => { + let value = supported || defaultValue; + this._supported = value.split(','); + }); }, trackTarget(target) { @@ -783,38 +799,56 @@ let performanceEntriesWatcher = { front.start(); front.on('entry', detail => { - if (detail.type === 'mark') { - let name = detail.name; - let epoch = detail.epoch; - let CHARS_UNTIL_APP_NAME = 7; // '@app://' - // FIXME There is a potential race condition that can result - // in some performance entries being disregarded. See bug 1189942. - if (name.indexOf('appLaunch') != -1) { - let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME; - let length = (name.indexOf('.') - appStartPos); - this._appLaunchName = name.substr(appStartPos, length); - this._appLaunchStartTime = epoch; - } else { - let origin = detail.origin; - origin = origin.substr(0, origin.indexOf('.')); - if (this._appLaunchName === origin) { - let time = epoch - this._appLaunchStartTime; - let eventName = 'app-startup-time-' + name; - - // Events based on performance marks are for telemetry only, they are - // not displayed in the HUD front end. - target._logHistogram({name: eventName, value: time}); - - memoryWatcher.front(target).residentUnique().then(value => { - eventName = 'app-memory-' + name; - target._logHistogram({name: eventName, value: value}); - }, err => { - console.error(err); - }); - } - } + // Only process performance marks. + if (detail.type !== 'mark') { + return; } + + let name = detail.name; + let epoch = detail.epoch; + + // FIXME There is a potential race condition that can result + // in some performance entries being disregarded. See bug 1189942. + // + // If this is an "app launch" mark, record the app that was + // launched and the epoch of when it was launched. + if (name.indexOf('appLaunch') !== -1) { + let CHARS_UNTIL_APP_NAME = 7; // '@app://' + let startPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME; + let endPos = name.indexOf('.'); + this._appLaunchName = name.slice(startPos, endPos); + this._appLaunchStartTime = epoch; + return; + } + + // Only process supported performance marks + if (this._supported.indexOf(name) === -1) { + return; + } + + let origin = detail.origin; + origin = origin.slice(0, origin.indexOf('.')); + + // Continue if the performance mark corresponds to the app + // for which we have recorded app launch information. + if (this._appLaunchName !== origin) { + return; + } + + let time = epoch - this._appLaunchStartTime; + let eventName = 'app_startup_time_' + name; + + // Events based on performance marks are for telemetry only, they are + // not displayed in the HUD front end. + target._logHistogram({name: eventName, value: time}); + + memoryWatcher.front(target).residentUnique().then(value => { + eventName = 'app_memory_' + name; + target._logHistogram({name: eventName, value: value}); + }, err => { + console.error(err); + }); }); }, diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js index 30bc8d0e9c19..d176689363ea 100644 --- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -573,6 +573,10 @@ let settingsToObserve = { 'devtools.remote.wifi.visible': { resetToPref: true }, + 'devtools.telemetry.supported_performance_marks': { + resetToPref: true + }, + 'dom.mozApps.use_reviewer_certs': false, 'dom.mozApps.signed_apps_installable_from': 'https://marketplace.firefox.com', 'dom.presentation.discovery.enabled': false, diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 42881aeb17ac..b63027262b52 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 3e10be06f1e0..aff5e63aece7 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index c857ce32b61a..1c0759865d1d 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index a4b6e960edc2..685525075560 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index c96ad5a13778..b1db33d69668 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 4c6cd155fa70..1fbc635cc2f5 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index c857ce32b61a..1c0759865d1d 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 6001923311f9..51fd4f200673 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 26cf10b26ccb..364aa2b4507f 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "e6994410dcc88bfead0e44620a065220b7f12290", + "git_revision": "d2e5c49440bf8410ae747b15c0dd11c54053ef3e", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "0611acb452ed9c34d02ccdf24850677490b716d1", + "revision": "4b08f8a2624bc83d1f839f79b4969671417c42d6", "repo_path": "integration/gaia-central" } diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 1ec80c8ba456..41edbe97e2e0 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 927a6fee4561..21aed1744054 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js index de398321c588..1a958b794b30 100644 --- a/browser/base/content/test/newtab/browser_newtab_block.js +++ b/browser/base/content/test/newtab/browser_newtab_block.js @@ -13,6 +13,7 @@ gDirectorySource = "data:application/json," + JSON.stringify({ imageURI: "data:image/png;base64,helloWORLD3", title: "title", type: "affiliate", + adgroup_name: "test", frecent_sites: ["example0.com"] }] }); diff --git a/browser/base/content/test/newtab/browser_newtab_bug1145428.js b/browser/base/content/test/newtab/browser_newtab_bug1145428.js index ba15b6be513b..f0d1ac6177c0 100644 --- a/browser/base/content/test/newtab/browser_newtab_bug1145428.js +++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js @@ -15,6 +15,7 @@ gDirectorySource = "data:application/json," + JSON.stringify({ enhancedImageURI: "data:image/png;base64,helloWORLD2", title: "title", type: "affiliate", + adgroup_name: "example", frecent_sites: ["example0.com"], }] }); diff --git a/browser/base/content/test/newtab/browser_newtab_bug1178586.js b/browser/base/content/test/newtab/browser_newtab_bug1178586.js index 32d887d4ca1d..dfc88942edbd 100644 --- a/browser/base/content/test/newtab/browser_newtab_bug1178586.js +++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js @@ -13,6 +13,7 @@ gDirectorySource = "data:application/json," + JSON.stringify({ enhancedImageURI: "data:image/png;base64,helloWORLD2", title: "title", type: "affiliate", + adgroup_name: "example", frecent_sites: ["example0.com"], }] }); diff --git a/browser/base/content/test/newtab/browser_newtab_enhanced.js b/browser/base/content/test/newtab/browser_newtab_enhanced.js index 4225f173b512..661327ab62e2 100644 --- a/browser/base/content/test/newtab/browser_newtab_enhanced.js +++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js @@ -8,6 +8,7 @@ let suggestedLink = { imageURI: "data:image/png;base64,helloWORLD3", title: "title2", type: "affiliate", + adgroup_name: "Technology", frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"] }; @@ -139,7 +140,7 @@ function runTests() { is(type, "affiliate", "suggested link is affiliate"); is(enhanced, "", "suggested link has no enhanced image"); is(title, "title2"); - ok(suggested.indexOf("Suggested for webdev education visitors") > -1, "Suggested for 'webdev education'"); + ok(suggested.indexOf("Suggested for Technology visitors") > -1, "Suggested for 'Technology'"); // Enhanced history link shows up second ({type, enhanced, title, suggested} = getData(1)); @@ -152,8 +153,7 @@ function runTests() { - // Test override category/adgroup name. - suggestedLink.adgroup_name = "Technology"; + // Test no override category/adgroup name. Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json," + JSON.stringify({"suggested": [suggestedLink]})); yield watchLinksChangeOnce().then(TestRunner.next); @@ -176,8 +176,8 @@ function runTests() { ok(suggested.indexOf("Suggested for Technology enthusiasts who visit sites like classroom.google.com ") > -1, "Suggested for 'Technology' enthusiasts"); - // Test server provided explanation string without category override. - delete suggestedLink.adgroup_name; + // Test server provided explanation string with category override. + suggestedLink.adgroup_name = "webdev education"; Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))); yield watchLinksChangeOnce().then(TestRunner.next); diff --git a/browser/components/extensions/ext-windows.js b/browser/components/extensions/ext-windows.js index f83bc4b81949..9c878827622a 100644 --- a/browser/components/extensions/ext-windows.js +++ b/browser/components/extensions/ext-windows.js @@ -60,7 +60,7 @@ extensions.registerAPI((extension, context) => { runSafe(context, callback, WindowManager.convert(extension, window, getInfo)); }, - getAll: function(getAll, callback) { + getAll: function(getInfo, callback) { let e = Services.wm.getEnumerator("navigator:browser"); let windows = []; while (e.hasMoreElements()) { @@ -131,7 +131,10 @@ extensions.registerAPI((extension, context) => { Services.focus.activeWindow = window; } // TODO: All the other properties... - runSafe(context, callback, WindowManager.convert(extension, window)); + + if (callback) { + runSafe(context, callback, WindowManager.convert(extension, window)); + } }, remove: function(windowId, callback) { diff --git a/browser/components/extensions/test/browser/browser.ini b/browser/components/extensions/test/browser/browser.ini index 2ce3ab84db03..a64633ab2d18 100644 --- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -6,3 +6,4 @@ skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac' [browser_ext_tabs_executeScript.js] [browser_ext_tabs_query.js] [browser_ext_tabs_update.js] +[browser_ext_windows_update.js] diff --git a/browser/components/extensions/test/browser/browser_ext_windows_update.js b/browser/components/extensions/test/browser/browser_ext_windows_update.js new file mode 100644 index 000000000000..eb70e2c33568 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js @@ -0,0 +1,51 @@ +add_task(function* () { + function promiseWaitForFocus(aWindow) { + return new Promise(function(aResolve, aReject) { + waitForFocus(function() { + ok(Services.focus.activeWindow === aWindow, "correct window focused"); + aResolve(); + }, aWindow); + }); + } + + let window1 = window; + let window2 = yield BrowserTestUtils.openNewBrowserWindow(); + + Services.focus.activeWindow = window2; + yield promiseWaitForFocus(window2); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": ["windows"] + }, + + background: function() { + browser.windows.getAll(undefined, function(wins) { + browser.test.assertEq(wins.length, 2, "should have two windows"); + + // Sort the unfocused window to the lower index. + wins.sort(function(win1, win2) { + if (win1.focused === win2.focused) { + return 0; + } + + return win1.focused ? 1 : -1; + }); + + browser.windows.update(wins[0].id, {focused: true}, function() { + browser.test.sendMessage("check"); + }); + + }); + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("check"); + + yield promiseWaitForFocus(window1); + + yield extension.unload(); + + yield BrowserTestUtils.closeWindow(window2); +}); \ No newline at end of file diff --git a/browser/devtools/canvasdebugger/snapshotslist.js b/browser/devtools/canvasdebugger/snapshotslist.js index 799cae2fce01..9af772ddc5c7 100644 --- a/browser/devtools/canvasdebugger/snapshotslist.js +++ b/browser/devtools/canvasdebugger/snapshotslist.js @@ -420,7 +420,7 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, { // Prepare all the function calls for serialization. yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => { - let { type, name, file, line, argsPreview, callerPreview } = call; + let { type, name, file, line, timestamp, argsPreview, callerPreview } = call; return call.getDetails().then(({ stack }) => { data.calls[i] = { type: type, @@ -428,6 +428,7 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, { file: file, line: line, stack: stack, + timestamp: timestamp, argsPreview: argsPreview, callerPreview: callerPreview }; diff --git a/browser/devtools/canvasdebugger/test/browser.ini b/browser/devtools/canvasdebugger/test/browser.ini index 6fdcb740dfcd..a806a06dee75 100644 --- a/browser/devtools/canvasdebugger/test/browser.ini +++ b/browser/devtools/canvasdebugger/test/browser.ini @@ -52,3 +52,5 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in [browser_canvas-frontend-stop-01.js] [browser_canvas-frontend-stop-02.js] [browser_canvas-frontend-stop-03.js] +[browser_profiling-canvas.js] +[browser_profiling-webgl.js] diff --git a/browser/devtools/canvasdebugger/test/browser_profiling-canvas.js b/browser/devtools/canvasdebugger/test/browser_profiling-canvas.js new file mode 100644 index 000000000000..4bf2647d24f8 --- /dev/null +++ b/browser/devtools/canvasdebugger/test/browser_profiling-canvas.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if functions inside a single animation frame are recorded and stored + * for a canvas context profiling. + */ + +function* ifTestingSupported() { + let currentTime = window.performance.now(); + let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL); + + let navigated = once(target, "navigate"); + + yield front.setup({ reload: true }); + ok(true, "The front was setup up successfully."); + + yield navigated; + ok(true, "Target automatically navigated when the front was set up."); + + let snapshotActor = yield front.recordAnimationFrame(); + ok(snapshotActor, + "A snapshot actor was sent after recording."); + + let animationOverview = yield snapshotActor.getOverview(); + ok(animationOverview, + "An animation overview could be retrieved after recording."); + + let functionCalls = animationOverview.calls; + ok(functionCalls, + "An array of function call actors was sent after recording."); + is(functionCalls.length, 8, + "The number of function call actors is correct."); + + info("Check the timestamps of function calls"); + + for ( let i = 0; i < functionCalls.length-1; i += 2 ) { + ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." ); + ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." ); + ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." ); + } + + yield removeTab(target.tab); + finish(); +} diff --git a/browser/devtools/canvasdebugger/test/browser_profiling-webgl.js b/browser/devtools/canvasdebugger/test/browser_profiling-webgl.js new file mode 100644 index 000000000000..3339ff3899a5 --- /dev/null +++ b/browser/devtools/canvasdebugger/test/browser_profiling-webgl.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if functions inside a single animation frame are recorded and stored + * for a canvas context profiling. + */ + +function* ifTestingSupported() { + let currentTime = window.performance.now(); + let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL); + + let navigated = once(target, "navigate"); + + yield front.setup({ reload: true }); + ok(true, "The front was setup up successfully."); + + yield navigated; + ok(true, "Target automatically navigated when the front was set up."); + + let snapshotActor = yield front.recordAnimationFrame(); + ok(snapshotActor, + "A snapshot actor was sent after recording."); + + let animationOverview = yield snapshotActor.getOverview(); + ok(animationOverview, + "An animation overview could be retrieved after recording."); + + let functionCalls = animationOverview.calls; + ok(functionCalls, + "An array of function call actors was sent after recording."); + is(functionCalls.length, 3, + "The number of function call actors is correct."); + + info("Check the timestamps of function calls"); + + for ( let i = 0; i < functionCalls.length-1; i += 2 ) { + ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." ); + ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." ); + ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." ); + } + + yield removeTab(target.tab); + finish(); +} diff --git a/browser/devtools/markupview/test/browser.ini b/browser/devtools/markupview/test/browser.ini index f7a6327327bc..bca4e5bfb4fe 100644 --- a/browser/devtools/markupview/test/browser.ini +++ b/browser/devtools/markupview/test/browser.ini @@ -23,6 +23,7 @@ support-files = doc_markup_toggle.html doc_markup_tooltip.png doc_markup_xul.xul + frame-script-utils.js head.js helper_attributes_test_runner.js helper_events_test_runner.js diff --git a/browser/devtools/markupview/test/browser_markupview_keybindings_04.js b/browser/devtools/markupview/test/browser_markupview_keybindings_04.js index 17cb16e88232..282a5349558a 100644 --- a/browser/devtools/markupview/test/browser_markupview_keybindings_04.js +++ b/browser/devtools/markupview/test/browser_markupview_keybindings_04.js @@ -45,10 +45,28 @@ function assertNodeSelected(inspector, tagName) { } function* selectWithBrowserMenu(inspector) { - yield BrowserTestUtils.synthesizeMouseAtCenter("div", { - type: "contextmenu", - button: 2 - }, gBrowser.selectedBrowser); + // This test can't use BrowserTestUtils.synthesizeMouseAtCenter() + // method (see below) since it causes intermittent test failures. + // So, we are introducing a new "Test:MarkupView:SynthesizeMouse" event + // that is handled in the content scope. The main difference between + // this new event and BrowserTestUtils library is EventUtils library. + // While BrowserTestUtils is using: + // chrome://mochikit/content/tests/SimpleTest/EventUtils.js + // (see: AsyncUtilsContent.js) + // ... this test requires: + // chrome://marionette/content/EventUtils.js + // (see markupview/test/frame-script-utils.js) + // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1199180 + yield executeInContent("Test:MarkupView:SynthesizeMouse", { + center: true, + selector: "div", + options: {type: "contextmenu", button: 2} + }); + + //yield BrowserTestUtils.synthesizeMouseAtCenter("div", { + // type: "contextmenu", + // button: 2 + //}, gBrowser.selectedBrowser); // nsContextMenu also requires the popupNode to be set, but we can't set it to // node under e10s as it's a CPOW, not a DOM node. But under e10s, @@ -70,9 +88,18 @@ function* selectWithBrowserMenu(inspector) { function* selectWithElementPicker(inspector) { yield inspector.toolbox.highlighterUtils.startPicker(); - yield BrowserTestUtils.synthesizeMouseAtCenter("div", { - type: "mousemove", - }, gBrowser.selectedBrowser); + + yield executeInContent("Test:MarkupView:SynthesizeMouse", { + center: true, + selector: "div", + options: {type: "mousemove"} + }); + + // Read comment in selectWithBrowserMenu() method. + //yield BrowserTestUtils.synthesizeMouseAtCenter("div", { + // type: "mousemove", + //}, gBrowser.selectedBrowser); + executeInContent("Test:SynthesizeKey", { key: "VK_RETURN", options: {} diff --git a/browser/devtools/markupview/test/frame-script-utils.js b/browser/devtools/markupview/test/frame-script-utils.js new file mode 100644 index 000000000000..5caa7aa33788 --- /dev/null +++ b/browser/devtools/markupview/test/frame-script-utils.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci} = Components; +const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); +let EventUtils = {}; +subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils); + +/** + * Synthesize a mouse event on an element. This handler doesn't send a message + * back. Consumers should listen to specific events on the inspector/highlighter + * to know when the event got synthesized. + * @param {Object} msg The msg.data part expects the following properties: + * - {Number} x + * - {Number} y + * - {Boolean} center If set to true, x/y will be ignored and + * synthesizeMouseAtCenter will be used instead + * - {Object} options Other event options + * - {String} selector An optional selector that will be used to find the node to + * synthesize the event on, if msg.objects doesn't contain the CPOW. + * The msg.objects part should be the element. + * @param {Object} data Event detail properties: + */ +addMessageListener("Test:MarkupView:SynthesizeMouse", function(msg) { + let {x, y, center, options, selector} = msg.data; + let {node} = msg.objects; + + if (!node && selector) { + node = content.document.querySelector(selector); + } + + if (center) { + EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView); + } else { + EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView); + } + + // Most consumers won't need to listen to this message, unless they want to + // wait for the mouse event to be synthesized and don't have another event + // to listen to instead. + sendAsyncMessage("Test:MarkupView:SynthesizeMouse"); +}); diff --git a/browser/devtools/markupview/test/head.js b/browser/devtools/markupview/test/head.js index c569901a77fe..edfa976df628 100644 --- a/browser/devtools/markupview/test/head.js +++ b/browser/devtools/markupview/test/head.js @@ -51,6 +51,7 @@ registerCleanupFunction(function*() { const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/"; const CHROME_BASE = "chrome://mochitests/content/browser/browser/devtools/markupview/test/"; const COMMON_FRAME_SCRIPT_URL = "chrome://browser/content/devtools/frame-script-utils.js"; +const MARKUPVIEW_FRAME_SCRIPT_URL = CHROME_BASE + "frame-script-utils.js"; /** * Add a new test tab in the browser and load the given url. @@ -71,6 +72,7 @@ function addTab(url) { info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL); linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false); + linkedBrowser.messageManager.loadFrameScript(MARKUPVIEW_FRAME_SCRIPT_URL, false); linkedBrowser.addEventListener("load", function onload() { linkedBrowser.removeEventListener("load", onload, true); diff --git a/browser/devtools/performance/modules/logic/frame-utils.js b/browser/devtools/performance/modules/logic/frame-utils.js index d79fe5e0c930..ca9d1dde5d82 100644 --- a/browser/devtools/performance/modules/logic/frame-utils.js +++ b/browser/devtools/performance/modules/logic/frame-utils.js @@ -554,6 +554,30 @@ function getFrameInfo (node, options) { } exports.getFrameInfo = getFrameInfo; + +/** + * Takes an inverted ThreadNode and searches its youngest frames for + * a FrameNode with matching location. + * + * @param {ThreadNode} threadNode + * @param {string} location + * @return {?FrameNode} + */ +function findFrameByLocation (threadNode, location) { + if (!threadNode.inverted) { + throw new Error("FrameUtils.findFrameByLocation only supports leaf nodes in an inverted tree."); + } + + let calls = threadNode.calls; + for (let i = 0; i < calls.length; i++) { + if (calls[i].location === location) { + return calls[i]; + } + } + return null; +} + +exports.findFrameByLocation = findFrameByLocation; exports.computeIsContentAndCategory = computeIsContentAndCategory; exports.parseLocation = parseLocation; exports.getInflatedFrameCache = getInflatedFrameCache; diff --git a/browser/devtools/performance/modules/logic/jit.js b/browser/devtools/performance/modules/logic/jit.js index 1a8b28d9ffc2..19584020dada 100644 --- a/browser/devtools/performance/modules/logic/jit.js +++ b/browser/devtools/performance/modules/logic/jit.js @@ -269,23 +269,13 @@ const IMPLEMENTATION_NAMES = Object.keys(IMPLEMENTATION_MAP); * @param {Array} sampleTimes * An array of every sample time within the range we're counting. * From a ThreadNode's `sampleTimes` property. - * @param {number} op.startTime - * The start time of the first sample. - * @param {number} op.endTime - * The end time of the last sample. - * @param {number} op.resolution - * The maximum amount of possible data points returned. - * Also determines the size in milliseconds of each bucket - * via `(endTime - startTime) / resolution` + * @param {number} bucketSize + * Size of each bucket in milliseconds. + * `duration / resolution = bucketSize` in OptimizationsGraph. * @return {?Array} */ -function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, endTime, resolution }) { - if (!frameNode.hasOptimizations()) { - return; - } - - let tierData = frameNode.getOptimizationTierData(); - let duration = endTime - startTime; +function createTierGraphDataFromFrameNode (frameNode, sampleTimes, bucketSize) { + let tierData = frameNode.getTierData(); let stringTable = frameNode._stringTable; let output = []; let implEnum; @@ -297,8 +287,9 @@ function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, let samplesInCurrentBucket = 0; let currentBucketStartTime = sampleTimes[0]; let bucket = []; - // Size of each bucket in milliseconds - let bucketSize = Math.ceil(duration / resolution); + + // Store previous data point so we can have straight vertical lines + let previousValues; // Iterate one after the samples, so we can finalize the last bucket for (let i = 0; i <= sampleTimes.length; i++) { @@ -310,19 +301,30 @@ function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, i >= sampleTimes.length) { let dataPoint = {}; - dataPoint.ys = []; - dataPoint.x = currentBucketStartTime; + dataPoint.values = []; + dataPoint.delta = currentBucketStartTime; // Map the opt site counts as a normalized percentage (0-1) // of its count in context of total samples this bucket for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) { - dataPoint.ys[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1); + dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1); } + + // Push the values from the previous bucket to the same time + // as the current bucket so we get a straight vertical line. + if (previousValues) { + let data = Object.create(null); + data.values = previousValues; + data.delta = currentBucketStartTime; + output.push(data); + } + output.push(dataPoint); // Set the new start time of this bucket and reset its count currentBucketStartTime += bucketSize; samplesInCurrentBucket = 0; + previousValues = dataPoint.values; bucket = []; } diff --git a/browser/devtools/performance/modules/logic/tree-model.js b/browser/devtools/performance/modules/logic/tree-model.js index 44e49df59f59..8a55d9b3928f 100644 --- a/browser/devtools/performance/modules/logic/tree-model.js +++ b/browser/devtools/performance/modules/logic/tree-model.js @@ -36,6 +36,8 @@ function ThreadNode(thread, options = {}) { this.calls = []; this.duration = options.endTime - options.startTime; this.nodeType = "Thread"; + this.inverted = options.invertTree; + // Total bytesize of all allocations if enabled this.byteSize = 0; this.youngestFrameByteSize = 0; @@ -232,10 +234,8 @@ ThreadNode.prototype = { leafTable); if (isLeaf) { frameNode.youngestFrameSamples++; - if (inflatedFrame.optimizations) { - frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation, - sampleTime, stringTable); - } + frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation, + sampleTime, stringTable); if (byteSize) { frameNode.youngestFrameByteSize += byteSize; @@ -410,7 +410,7 @@ function FrameNode(frameKey, { location, line, category, isContent }, isMetaCate this.calls = []; this.isContent = !!isContent; this._optimizations = null; - this._tierData = null; + this._tierData = []; this._stringTable = null; this.isMetaCategory = !!isMetaCategory; this.category = category; @@ -427,8 +427,8 @@ FrameNode.prototype = { * Any JIT optimization information attached to the current * sample. Lazily inflated via stringTable. * @param number implementation - * JIT implementation used for this observed frame (interpreter, - * baseline, ion); + * JIT implementation used for this observed frame (baseline, ion); + * can be null indicating "interpreter" * @param number time * The time this optimization occurred. * @param object stringTable @@ -441,16 +441,16 @@ FrameNode.prototype = { let opts = this._optimizations; if (opts === null) { opts = this._optimizations = []; - this._stringTable = stringTable; } opts.push(site); - - if (this._tierData === null) { - this._tierData = []; - } - // Record type of implementation used and the sample time - this._tierData.push({ implementation, time }); } + + if (!this._stringTable) { + this._stringTable = stringTable; + } + + // Record type of implementation used and the sample time + this._tierData.push({ implementation, time }); }, _clone: function (samples, size) { @@ -474,17 +474,29 @@ FrameNode.prototype = { this.youngestFrameByteSize += otherNode.youngestFrameByteSize; } + if (this._stringTable === null) { + this._stringTable = otherNode._stringTable; + } + if (otherNode._optimizations) { - let opts = this._optimizations; - if (opts === null) { - opts = this._optimizations = []; - this._stringTable = otherNode._stringTable; + if (!this._optimizations) { + this._optimizations = []; } + let opts = this._optimizations; let otherOpts = otherNode._optimizations; for (let i = 0; i < otherOpts.length; i++) { - opts.push(otherOpts[i]); + opts.push(otherOpts[i]); } } + + if (otherNode._tierData.length) { + let tierData = this._tierData; + let otherTierData = otherNode._tierData; + for (let i = 0; i < otherTierData.length; i++) { + tierData.push(otherTierData[i]); + } + tierData.sort((a, b) => a.time - b.time); + } }, /** @@ -529,14 +541,11 @@ FrameNode.prototype = { }, /** - * Returns the optimization tiers used overtime. + * Returns the tiers used overtime. * - * @return {?Array} + * @return {Array} */ - getOptimizationTierData: function () { - if (!this._tierData) { - return null; - } + getTierData: function () { return this._tierData; } }; diff --git a/browser/devtools/performance/modules/widgets/graphs.js b/browser/devtools/performance/modules/widgets/graphs.js index a7f5d47b822d..f3bfe65966b2 100644 --- a/browser/devtools/performance/modules/widgets/graphs.js +++ b/browser/devtools/performance/modules/widgets/graphs.js @@ -12,6 +12,7 @@ const { Task } = require("resource://gre/modules/Task.jsm"); const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm"); const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget"); const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget"); +const MountainGraphWidget = require("devtools/shared/widgets/MountainGraphWidget"); const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs"); loader.lazyRequireGetter(this, "promise"); @@ -28,6 +29,8 @@ loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); loader.lazyRequireGetter(this, "MarkersOverview", "devtools/performance/markers-overview", true); +loader.lazyRequireGetter(this, "createTierGraphDataFromFrameNode", + "devtools/performance/jit", true); /** * For line graphs @@ -37,9 +40,9 @@ const STROKE_WIDTH = 1; // px const DAMPEN_VALUES = 0.95; const CLIPHEAD_LINE_COLOR = "#666"; const SELECTION_LINE_COLOR = "#555"; -const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue"; -const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green"; -const MEMORY_GRAPH_COLOR_NAME = "highlight-blue"; +const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue"; +const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green"; +const MEMORY_GRAPH_COLOR_NAME = "graphs-blue"; /** * For timeline overview @@ -48,6 +51,11 @@ const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px const MARKERS_GRAPH_ROW_HEIGHT = 10; // px const MARKERS_GROUP_VERTICAL_PADDING = 4; // px +/** + * For optimization graph + */ +const OPTIMIZATIONS_GRAPH_RESOLUTION = 100; + /** * A base class for performance graphs to inherit from. * @@ -86,7 +94,7 @@ PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, { */ setTheme: function (theme) { theme = theme || "light"; - let mainColor = getColor(this.mainColor || "highlight-blue", theme); + let mainColor = getColor(this.mainColor || "graphs-blue", theme); this.backgroundColor = getColor("body-background", theme); this.strokeColor = mainColor; this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2); @@ -425,6 +433,82 @@ GraphsController.prototype = { }), }; +/** + * A base class for performance graphs to inherit from. + * + * @param nsIDOMNode parent + * The parent node holding the overview. + * @param string metric + * The unit of measurement for this graph. + */ +function OptimizationsGraph(parent) { + MountainGraphWidget.call(this, parent); + this.setTheme(); +} + +OptimizationsGraph.prototype = Heritage.extend(MountainGraphWidget.prototype, { + + render: Task.async(function *(threadNode, frameNode) { + // Regardless if we draw or clear the graph, wait + // until it's ready. + yield this.ready(); + + if (!threadNode || !frameNode) { + this.setData([]); + return; + } + + let { sampleTimes } = threadNode; + + if (!sampleTimes.length) { + this.setData([]); + return; + } + + // Take startTime/endTime from samples recorded, rather than + // using duration directly from threadNode, as the first sample that + // equals the startTime does not get recorded. + let startTime = sampleTimes[0]; + let endTime = sampleTimes[sampleTimes.length - 1]; + + let bucketSize = (endTime - startTime) / OPTIMIZATIONS_GRAPH_RESOLUTION; + let data = createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize); + + // If for some reason we don't have data (like the frameNode doesn't + // have optimizations, but it shouldn't be at this point if it doesn't), + // log an error. + if (!data) { + Cu.reportError(`FrameNode#${frameNode.location} does not have optimizations data to render.`); + return; + } + + this.dataOffsetX = startTime; + yield this.setData(data); + }), + + /** + * Sets the theme via `theme` to either "light" or "dark", + * and updates the internal styling to match. Requires a redraw + * to see the effects. + */ + setTheme: function (theme) { + theme = theme || "light"; + + let interpreterColor = getColor("graphs-red", theme); + let baselineColor = getColor("graphs-blue", theme); + let ionColor = getColor("graphs-green", theme); + + this.format = [ + { color: interpreterColor }, + { color: baselineColor }, + { color: ionColor }, + ]; + + this.backgroundColor = getColor("sidebar-background", theme); + } +}); + +exports.OptimizationsGraph = OptimizationsGraph; exports.FramerateGraph = FramerateGraph; exports.MemoryGraph = MemoryGraph; exports.TimelineGraph = TimelineGraph; diff --git a/browser/devtools/performance/performance-controller.js b/browser/devtools/performance/performance-controller.js index c47932ea9cbf..63ed9e54ca86 100644 --- a/browser/devtools/performance/performance-controller.js +++ b/browser/devtools/performance/performance-controller.js @@ -33,6 +33,8 @@ loader.lazyRequireGetter(this, "RecordingUtils", "devtools/toolkit/performance/utils"); loader.lazyRequireGetter(this, "GraphsController", "devtools/performance/graphs", true); +loader.lazyRequireGetter(this, "OptimizationsGraph", + "devtools/performance/graphs", true); loader.lazyRequireGetter(this, "WaterfallHeader", "devtools/performance/waterfall-ticks", true); loader.lazyRequireGetter(this, "MarkerView", @@ -43,6 +45,8 @@ loader.lazyRequireGetter(this, "MarkerUtils", "devtools/performance/marker-utils"); loader.lazyRequireGetter(this, "WaterfallUtils", "devtools/performance/waterfall-utils"); +loader.lazyRequireGetter(this, "FrameUtils", + "devtools/performance/frame-utils"); loader.lazyRequireGetter(this, "CallView", "devtools/performance/tree-view", true); loader.lazyRequireGetter(this, "ThreadNode", diff --git a/browser/devtools/performance/performance.xul b/browser/devtools/performance/performance.xul index 8420e90c1544..ebb87d528682 100644 --- a/browser/devtools/performance/performance.xul +++ b/browser/devtools/performance/performance.xul @@ -77,7 +77,7 @@ - + @@ -304,6 +304,7 @@ + @@ -364,8 +365,7 @@ - - + diff --git a/browser/devtools/performance/test/unit/test_jit-graph-data.js b/browser/devtools/performance/test/unit/test_jit-graph-data.js index 1558e56ff908..ac3c3258de32 100644 --- a/browser/devtools/performance/test/unit/test_jit-graph-data.js +++ b/browser/devtools/performance/test/unit/test_jit-graph-data.js @@ -36,27 +36,41 @@ add_task(function test() { equal(root.sampleTimes[root.sampleTimes.length - 1], endTime, "root recorded last sample time in scope"); let frame = getFrameNodePath(root, "X"); - let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, { startTime, endTime, resolution: RESOLUTION }); + let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, (endTime-startTime)/RESOLUTION); let TIME_PER_WINDOW = SAMPLE_COUNT / 2 / RESOLUTION * TIME_PER_SAMPLE; - for (let i = 0; i < 10; i++) { - equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x"); - equal(data[i].ys[0], 0.2, "first window has 2 frames in interpreter"); - equal(data[i].ys[1], 0.2, "first window has 2 frames in baseline"); - equal(data[i].ys[2], 0.2, "first window has 2 frames in ion"); + // Filter out the dupes created with the same delta so the graph + // can render correctly. + let filteredData = []; + for (let i = 0; i < data.length; i++) { + if (!i || data[i].delta !== data[i-1].delta) { + filteredData.push(data[i]); + } } - for (let i = 10; i < 20; i++) { - equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x"); - equal(data[i].ys[0], 0, "second window observed no optimizations"); - equal(data[i].ys[1], 0, "second window observed no optimizations"); - equal(data[i].ys[2], 0, "second window observed no optimizations"); + data = filteredData; + + for (let i = 0; i < 11; i++) { + equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x"); + equal(data[i].values[0], 0.2, "first window has 2 frames in interpreter"); + equal(data[i].values[1], 0.2, "first window has 2 frames in baseline"); + equal(data[i].values[2], 0.2, "first window has 2 frames in ion"); } - for (let i = 20; i < 30; i++) { - equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x"); - equal(data[i].ys[0], 0.3, "third window has 3 frames in interpreter"); - equal(data[i].ys[1], 0, "third window has 0 frames in baseline"); - equal(data[i].ys[2], 0, "third window has 0 frames in ion"); + // Start on 11, since i===10 is where the values change, and the new value (0,0,0) + // is removed in `filteredData` + for (let i = 11; i < 20; i++) { + equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x"); + equal(data[i].values[0], 0, "second window observed no optimizations"); + equal(data[i].values[1], 0, "second window observed no optimizations"); + equal(data[i].values[2], 0, "second window observed no optimizations"); + } + // Start on 21, since i===20 is where the values change, and the new value (0.3,0,0) + // is removed in `filteredData` + for (let i = 21; i < 30; i++) { + equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x"); + equal(data[i].values[0], 0.3, "third window has 3 frames in interpreter"); + equal(data[i].values[1], 0, "third window has 0 frames in baseline"); + equal(data[i].values[2], 0, "third window has 0 frames in ion"); } }); @@ -68,20 +82,19 @@ function uniqStr(s) { const TIER_PATTERNS = [ // 0-99 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 100-199 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 200-299 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 300-399 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 400-499 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 500-599 - // Test current frames in all opts, including that - // the same frame with no opts does not get counted - ["X", "X", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"], + // Test current frames in all opts + ["A", "A", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"], // 600-699 // Nothing for current frame @@ -92,9 +105,9 @@ const TIER_PATTERNS = [ ["X_2 -> Y", "X_2 -> Y", "X_2 -> Y", "X_0", "X_0", "X_0", "A", "A", "A", "A"], // 800-899 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], // 900-999 - ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"], + ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"], ]; function createSample (i, frames) { diff --git a/browser/devtools/performance/views/details-js-call-tree.js b/browser/devtools/performance/views/details-js-call-tree.js index 10002eb30a81..ba423eba3212 100644 --- a/browser/devtools/performance/views/details-js-call-tree.js +++ b/browser/devtools/performance/views/details-js-call-tree.js @@ -36,6 +36,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, { destroy: function () { OptimizationsListView.destroy(); this.container = null; + this.threadNode = null; DetailsSubview.destroy.call(this); }, @@ -56,7 +57,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, { flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"), showOptimizationHint: optimizations }; - let threadNode = this._prepareCallTree(profile, interval, options); + let threadNode = this.threadNode = this._prepareCallTree(profile, interval, options); this._populateCallTree(threadNode, options); if (optimizations) { @@ -79,7 +80,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, { _onFocus: function (_, treeItem) { if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) { - OptimizationsListView.setCurrentFrame(treeItem.frame); + OptimizationsListView.setCurrentFrame(this.threadNode, treeItem.frame); OptimizationsListView.render(); } diff --git a/browser/devtools/performance/views/optimizations-list.js b/browser/devtools/performance/views/optimizations-list.js index fc3b384b8eb4..458d2b60d752 100644 --- a/browser/devtools/performance/views/optimizations-list.js +++ b/browser/devtools/performance/views/optimizations-list.js @@ -24,6 +24,7 @@ let OptimizationsListView = { */ initialize: function () { this.reset = this.reset.bind(this); + this._onThemeChanged = this._onThemeChanged.bind(this); this.el = $("#jit-optimizations-view"); this.$headerName = $("#jit-optimizations-header .header-function-name"); @@ -34,15 +35,20 @@ let OptimizationsListView = { sorted: false, emptyText: JIT_EMPTY_TEXT }); + this.graph = new OptimizationsGraph($("#optimizations-graph")); + this.graph.setTheme(PerformanceController.getTheme()); // Start the tree by resetting. this.reset(); + + PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged); }, /** * Destruction function called when the tool cleans up. */ destroy: function () { + PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged); this.tree = null; this.$headerName = this.$headerFile = this.$headerLine = this.el = null; }, @@ -53,7 +59,10 @@ let OptimizationsListView = { * * @param {FrameNode} frameNode */ - setCurrentFrame: function (frameNode) { + setCurrentFrame: function (threadNode, frameNode) { + if (threadNode !== this.getCurrentThread()) { + this._currentThread = threadNode; + } if (frameNode !== this.getCurrentFrame()) { this._currentFrame = frameNode; } @@ -64,16 +73,25 @@ let OptimizationsListView = { * * @return {?FrameNode} */ - getCurrentFrame: function (frameNode) { + getCurrentFrame: function () { return this._currentFrame; }, + /** + * Returns the current thread node for this view. + * + * @return {?ThreadNode} + */ + getCurrentThread: function () { + return this._currentThread; + }, + /** * Clears out data in the tree, sets to an empty state, * and removes current frame. */ reset: function () { - this.setCurrentFrame(null); + this.setCurrentFrame(null, null); this.clear(); this.el.classList.add("empty"); this.emit(EVENTS.OPTIMIZATIONS_RESET); @@ -123,9 +141,18 @@ let OptimizationsListView = { this._renderSite(view, site, frameData); } + this._renderTierGraph(); + this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame()); }, + /** + * Renders the optimization tier graph over time. + */ + _renderTierGraph: function () { + this.graph.render(this.getCurrentThread(), this.getCurrentFrame()); + }, + /** * Creates an entry in the tree widget for an optimization site. */ @@ -175,7 +202,6 @@ let OptimizationsListView = { /** * Creates an element for insertion in the raw view for an OptimizationSite. */ - _createSiteNode: function (frameData, site) { let node = document.createElement("span"); let desc = document.createElement("span"); @@ -225,7 +251,6 @@ let OptimizationsListView = { * @param {IonType} ionType * @return {Element} */ - _createIonNode: function (ionType) { let node = document.createElement("span"); node.textContent = `${ionType.site} : ${ionType.mirType}`; @@ -240,7 +265,6 @@ let OptimizationsListView = { * @param {ObservedType} type * @return {Element} */ - _createObservedTypeNode: function (type) { let node = document.createElement("span"); let typeNode = document.createElement("span"); @@ -280,7 +304,6 @@ let OptimizationsListView = { * @param {OptimizationAttempt} attempt * @return {Element} */ - _createAttemptNode: function (attempt) { let node = document.createElement("span"); let strategyNode = document.createElement("span"); @@ -309,7 +332,6 @@ let OptimizationsListView = { * @param {?Element} el * @return {Element} */ - _createDebuggerLinkNode: function (url, line, el) { let node = el || document.createElement("span"); node.className = "opt-url"; @@ -329,7 +351,6 @@ let OptimizationsListView = { /** * Updates the headers with the current frame's data. */ - _setHeaders: function (frameData) { let isMeta = frameData.isMetaCategory; let name = isMeta ? frameData.categoryData.label : frameData.functionName; @@ -351,7 +372,6 @@ let OptimizationsListView = { * @param {String} url * @return {Boolean} */ - _isLinkableURL: function (url) { return url && url.indexOf && (url.indexOf("http") === 0 || @@ -359,6 +379,14 @@ let OptimizationsListView = { url.indexOf("file://") === 0); }, + /** + * Called when `devtools.theme` changes. + */ + _onThemeChanged: function (_, theme) { + this.graph.setTheme(theme); + this.graph.refresh({ force: true }); + }, + toString: () => "[object OptimizationsListView]" }; diff --git a/browser/devtools/responsivedesign/responsivedesign.jsm b/browser/devtools/responsivedesign/responsivedesign.jsm index f76a625f3681..0c3ed8c222c4 100644 --- a/browser/devtools/responsivedesign/responsivedesign.jsm +++ b/browser/devtools/responsivedesign/responsivedesign.jsm @@ -389,6 +389,7 @@ ResponsiveUI.prototype = { // Toolbar this.toolbar = this.chromeDoc.createElement("toolbar"); this.toolbar.className = "devtools-responsiveui-toolbar"; + this.toolbar.setAttribute("fullscreentoolbar", "true"); this.menulist = this.chromeDoc.createElement("menulist"); this.menulist.className = "devtools-responsiveui-menulist"; diff --git a/browser/devtools/shared/widgets/MountainGraphWidget.js b/browser/devtools/shared/widgets/MountainGraphWidget.js index 58a6f2df80c7..ccc5e7d58759 100644 --- a/browser/devtools/shared/widgets/MountainGraphWidget.js +++ b/browser/devtools/shared/widgets/MountainGraphWidget.js @@ -12,7 +12,7 @@ const HTML_NS = "http://www.w3.org/1999/xhtml"; const GRAPH_DAMPEN_VALUES_FACTOR = 0.9; const GRAPH_BACKGROUND_COLOR = "#ddd"; -const GRAPH_STROKE_WIDTH = 2; // px +const GRAPH_STROKE_WIDTH = 1; // px const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)"; const GRAPH_HELPER_LINES_DASH = [5]; // px const GRAPH_HELPER_LINES_WIDTH = 1; // px @@ -125,8 +125,8 @@ MountainGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { let totalSections = this.format.length; let totalTicks = this._data.length; - let firstTick = this._data[0].delta; - let lastTick = this._data[totalTicks - 1].delta; + let firstTick = totalTicks ? this._data[0].delta : 0; + let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0; let duration = this.dataDuration || lastTick; let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX); diff --git a/browser/docs/DirectoryLinksProvider.rst b/browser/docs/DirectoryLinksProvider.rst index db8a57048ad3..c6f55c3d4b26 100644 --- a/browser/docs/DirectoryLinksProvider.rst +++ b/browser/docs/DirectoryLinksProvider.rst @@ -194,8 +194,7 @@ A suggested link has additional values: %2$S is replaced by the triggering site. - ``frecent_sites`` - array of strings of the sites that can trigger showing a Suggested Tile if the user has the site in one of the top 100 most-frecent - pages. Only preapproved array of strings that are hardcoded into the - DirectoryLinksProvider module are allowed. + pages. - ``frequency_caps`` - an object consisting of daily and total frequency caps that limit the number of times a Suggested Tile can be shown in the new tab per day and overall. diff --git a/browser/modules/DirectoryLinksProvider.jsm b/browser/modules/DirectoryLinksProvider.jsm index ebaea279d85e..170e08f75890 100644 --- a/browser/modules/DirectoryLinksProvider.jsm +++ b/browser/modules/DirectoryLinksProvider.jsm @@ -64,178 +64,6 @@ const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping"; // The preference that tells if newtab is enhanced const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced"; -// Only allow explicitly approved frecent sites with display name -const ALLOWED_FRECENT_SITES = new Map([ - [ '1800petmeds.com,800petmeds.com,adopt.dogtime.com,adoptapet.com,akc.org,americanhumane.org,animal.discovery.com,animalconcerns.org,animalshelter.org,arcatapet.com,aspca.org,avma.org,bestfriends.org,blog.petmeds.com,buddydoghs.com,carealotpets.com,dailypuppy.com,dog.com,dogbar.com,dogbreedinfo.com,drsfostersmith.com,entirelypets.com,farmsanctuary.org,farmusa.org,freekibble.com,freekibblekat.com,healthypets.com,hsus.org,humanesociety.org,liveaquaria.com,marinedepot.com,medi-vet.com,nationalpetpharmacy.com,nsalamerica.org,nycacc.org,ohmydogsupplies.com,pet-dog-cat-supply-store.com,petcarerx.com,petco.com,petdiscounters.com,petedge.com,peteducation.com,petfinder.com,petfooddirect.com,petguys.com,petharbor.com,petmountain.com,petplanet.co.uk,pets911.com,petsmart.com,petsuppliesplus.com,puppyfind.com,revivalanimal.com,terrificpets.com,thatpetplace.com,theanimalrescuesite.com,theanimalrescuesite.greatergood.com,thefluffingtonpost.com,therainforestsite.com,vetdepot.com', - 'pet' ], - [ '1aauto.com,autoblog.com,autoguide.com,autosite.com,autoweek.com,bimmerpost.com,bmwblog.com,boldride.com,caranddriver.com,carcomplaints.com,carspoon.com,cherokeeforum.com,classiccars.com,commercialtrucktrader.com,corvetteforum.com,dealerrater.com,ebizautos.com,ford-trucks.com,hemmings.com,jalopnik.com,jeepforum.com,jeepsunlimited.com,jk-forum.com,legendaryspeed.com,motorauthority.com,motortrend.com,motorwings.com,odometer.com,pirate4x4.com,purecars.com,roadandtrack.com,teslamotorsclub.com,topgear.com,topspeed.com,totalmini.com,truckpaper.com,wranglerforum.com', - 'auto' ], - [ 'autobytel.com,autocheck.com,automotive.com,autonation.com,autos.aol.com,autos.msn.com,autos.yahoo.com,autotrader.autos.msn.com,autotrader.com,autotraderclassics.com,autoweb.com,car.com,carbuyingtips.com,carfax.com,cargurus.com,carmax.com,carprices.com,cars.com,cars.oodle.com,carsdirect.com,carsforsale.com,edmunds.com,hertzcarsales.com,imotors.com,intellichoice.com,internetautoguide.com,kbb.com,lemonfree.com,nada.com,nadaguides.com,thecarconnection.com,thetruthaboutcars.com,truecar.com,usedcars.com,usnews.rankingsandreviews.com', - 'auto' ], - [ 'acura.com,audi.ca,audi.com,audiusa.com,automobiles.honda.com,bentleymotors.com,bmw.com,bmwusa.com,buick.com,buyatoyota.com,cadillac.com,cars.mclaren.com,chevrolet.com,choosenissan.com,chrysler.com,daimler.com,dodge.com,ferrari.com/en_us,fiskerautomotive.com,ford.com,gm.com,gmc.com,hummer.com,hyundai.com,hyundaiusa.com,infiniti.com,infinitiusa.com,jaguarusa.com,jeep.com,kia.com,kiamotors.com,lamborghini.com/en/home,landrover.com,landroverusa.com,lexus.com,lincoln.com,maserati.us,mazda.com,mazdausa.com,mbusa.com,mbusi.com,mercedes-amg.com,mercedes-benz.com,mercuryvehicles.com,miniusa.com,nissanusa.com,pontiac.com,porsche.com/usa,ramtrucks.com,rolls-roycemotorcars.com,saturn.com,scion.com,subaru.com,teslamotors.com,toyota.com,volkswagen.co.uk,volkswagen.com,volvocars.com/us,vw.com', - 'auto' ], - [ '1010tires.com,4wheelparts.com,advanceautoparts.com,andysautosport.com,autoanything.com,autogeek.net,autopartsgiant.com,autopartswarehouse.com,autotrucktoys.com,autozone.com,autozoneinc.com,bavauto.com,bigotires.com,bilsteinus.com,brembo.com,car-part.com,carid.com,carparts.com,carquest.com,dinancars.com,discounttire.com,discounttiredirect.com,firestonecompleteautocare.com,goodyear.com,hrewheels,jcwhitney.com,kw-suspensions.com,momousa.com,napaonline.com,onlinetires.com,oreillyauto.com,oriellysautoparts.com,pepboys.com,repairpal.com,rockauto.com,shop.advanceautoparts.com,slickcar.com,stoptech.com,streetbeatcustoms.com,summitracing.com,tirebuyer.com,tirerack.com,tiresplus.com,tsw.com,velocitymotoring.com,wheelmax.com', - 'auto parts' ], - [ 'abebooks.co.uk,abebooks.com,addall.com,alibris.com,allaboutcircuits.com,allbookstores.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biblio.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,goodreads.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,usedbooksearch.co.uk,valorebooks.com,valuemags.com,vialibri.net,wwnorton.com,zoobooks.com', - 'literature' ], - [ '53.com,ally.com,bankofamerica.com,bbt.com,bnymellon.com,capitalone.com/bank/,chase.com,citi.com,citibank.com,citizensbank.com,citizensbankonline.com,creditonebank.com,everbank.com,hsbc.com,key.com,pnc.com,pncbank.com,rbs.co.uk,regions.com,sovereignbank.com,suntrust.com,tdbank.com,usaa.com,usbank.com,wachovia.com,wamu.com,wellsfargo.com,wsecu.org', - 'banking' ], - [ '247wallst.com,bizjournals.com,bloomberg.com,businessweek.com,cnbc.com,cnnmoney.com,dowjones.com,easyhomesite.com,economist.com,entrepreneur.com,fastcompany.com,finance.yahoo.com,forbes.com,fortune.com,foxbusiness.com,ft.com,hbr.org,ibtimes.com,inc.com,manta.com,marketwatch.com,newsweek.com,online.wsj.com,qz.com,reuters.com,smartmoney.com,wsj.com', - 'business news' ], - [ 'achievecard.com,americanexpress.com,barclaycardus.com,card.com,citicards.com,comparecards.com,creditcards.citi.com,discover.com,discovercard.com,experian.com,skylightpaycard.com,squareup.com,visa.com,visabuxx.com,visaextras.com', - 'finance' ], - [ 'alliantcreditunion.org,connexuscu.org,lmcu.org,nasafcu.com,navyfcu.org,navyfederal.org,penfed.org,sccu.com,suncoastcreditunion.com,tinkerfcu.org,veridiancu.org', - 'finance' ], - [ 'allbusiness.com,bankrate.com,buyersellertips.com,cboe.com,cnbcprime.com,coindesk.com,dailyfinance.com,dailyfx.com,dealbreaker.com,easierstreetdaily.com,economywatch.com,etfdailynews.com,etfdb.com,financeformulas.net,finviz.com,fool.com,forexpros.com,forexthreads.com,ftpress.com,fx-exchange.com,insidermonkey.com,investmentu.com,investopedia.com,investorjunkie.com,investors.com,kiplinger.com,minyanville.com,moneymorning.com,moneyning.com,moneysavingexpert.com,morningstar.com,nakedcapitalism.com,ncsoft.net,oilprice.com,realclearmarkets.com,rttnews.com,seekingalpha.com,silverdoctors.com,stockcharts.com,stockpickr.com,thefinancials.com,thestreet.com,wallstreetinsanity.com,wikinvest.com,xe.com,youngmoney.com', - 'investing' ], - [ 'edwardjones.com,fidelity.com,goldmansachs.com,jpmorgan.com,ml.com,morganstanley.com,mymerrill.com,personal.vanguard.com,principal.com,schwab.com,schwabplan.com,scottrade.com,tdameritrade.com,troweprice.com,vanguard.com', - 'investing' ], - [ '247lendinggroup.com,americanoneunsecured.com,avant.com,bestegg.com,chasestudentloans.com,eloan.com,gofundme.com,guidetolenders.com,kiva.org,lendacademy.com,lendingclub.com,lendingtree.com,lightstream.com,loanio.com,manageyourloans.com,meetearnest.com,microplace.com,netcredit.com,peer-lend.com,personalloans.com,prosper.com,salliemae.com,sofi.com,springleaf.com,uk.zopa.com,upstart.com', - 'finance' ], - [ 'betterment.com,blooom.com,futureadvisor.com,kapitall.com,motifinvesting.com,personalcapital.com,wealthfront.com,wisebanyan.com', - 'investing' ], - [ 'bancdebinary.com,cherrytrade.com,empireoption.net,etrade.com,firstrade.com,forex.com,interactivebrokers.com,ishares.com,optionsxpress.com,sharebuilder.com,thinkorswim.com,tradeking.com,trademonster.com,us.etrade.com,zecco.com', - 'finance' ], - [ 'annualcreditreport.com,bluebird.com,credio.com,creditkarma.com,creditreport.com,cybersource.com,equifax.com,freecreditreport.com,freecreditscore.com,freedomdebtrelief.com,freescoreonline.com,mint.com,moneymappress.com,myfico.com,nationaldebtrelief.com,onesmartpenny.com,paypal.com,transunion.com,truecredit.com,upromise.com,vuebill.com,xpressbillpay.com,youneedabudget.com', - 'personal finance' ], - [ 'angieslist.com,bloomberg.com,businessinsider.com,buydomains.com,domain.com,entrepreneur.com,fastcompany.com,forbes.com,fortune.com,godaddy.com,inc.com,manta.com,nytimes.com,openforum.com,register.com,salesforce.com,sba.gov,sbomag.com,shopsmall.americanexpress.com,smallbusiness.yahoo.com,squarespace.com,startupjournal.com,startupnation.com,weebly.com,wordpress.com,youngentrepreneur.com', - 'business news' ], - [ '1040now.net,24hourtax.com,acttax.com,comparetaxsoftware.org,e-file.com,etax.com,free1040taxreturn.com,hrblock.com,intuit.com,irstaxdoctors.com,libertytax.com,octaxcol.com,pay1040.com,priortax.com,quickbooks.com,quickrefunds.com,rapidtax.com,refundschedule.com,taxact.com,taxactonline.com,taxefile.com,taxhead.com,taxhelptoday.me,taxsimple.org,turbotax.com', - 'tax' ], - [ 'adeccousa.com,americasjobexchange.com,aoljobs.com,applicantpro.com,applicantstack.com,apply-4-jobs.com,apply2jobs.com,att.jobs,beyond.com,careerboutique.com,careerbuilder.com,careerflash.net,careerslocal.net,climber.com,coverlettersandresume.com,dice.com,diversityonecareers.com,employmentguide.com,everyjobforme.com,experteer.com,find.ly,findtherightjob.com,freelancer.com,gigats.com,glassdoor.com,governmentjobs.com,hrapply.com,hrdepartment.com,hrsmart.com,ihire.com,indeed.com,internships.com,itsmycareer.com,job-applications.com,job-hunt.org,job-interview-site.com,job.com,jobcentral.com,jobdiagnosis.com,jobhat.com,jobing.com,jobrapido.com,jobs.aol.com,jobs.net,jobsbucket.com,jobsflag.com,jobsgalore.com,jobsonline.com,jobsradar.com,jobster.com,jobtorch.com,jobungo.com,jobvite.com,juju.com,linkedin.com,livecareer.com,localjobster.com,mindtools.com,monster.com,myjobhelper.com,myperfectresume.com,payscale.com,pryor.com,quintcareers.com,randstad.com,recruitingcenter.net,resume-library.com,resume-now.com,roberthalf.com,salary.com,salaryexpert.com,simplyhired.com,smartrecruiters.com,snagajob.com,startwire.com,theladders.com,themuse.com,theresumator.com,thingamajob.com,usajobs.gov,ziprecruiter.com', - 'career services' ], - [ 'americanheart.org,americanredcross.com,americares.org,catholiccharitiesusa.org,charitybuzz.com,charitynavigator.org,charitywater.org,directrelief.org,fao.org,habitat.org,hrw.org,imf.org,mskcc.org,ohchr.org,redcross.org,reliefweb.int,salvationarmyusa.org,savethechildren.org,un.org,undp.org,unep.org,unesco.org,unfpa.org,unhcr.org,unicef.org,unicefusa.org,unops.org,volunteermatch.org,wfp.org,who.int,worldbank.org', - 'philanthropic' ], - [ 'academia.edu,albany.edu,american.edu,amity.edu,annauniv.edu,apus.edu,arizona.edu,ashford.edu,asu.edu,auburn.edu,austincc.edu,baylor.edu,bc.edu,berkeley.edu,brandeis.edu,brookings.edu,brown.edu,bu.edu,buffalo.edu,byu.edu,calpoly.edu,calstate.edu,caltech.edu,cam.ac.uk,cambridge.org,capella.edu,case.edu,clemson.edu,cmu.edu,colorado.edu,colostate-pueblo.edu,colostate.edu,columbia.edu,commnet.edu,cornell.edu,cpp.edu,csulb.edu,csun.edu,csus.edu,cuny.edu,cwru.edu,dartmouth.edu,depaul.edu,devry.edu,drexel.edu,du.edu,duke.edu,emory.edu,fau.edu,fcps.edu,fiu.edu,fordham.edu,fsu.edu,fullerton.edu,fullsail.edu,gatech.edu,gcu.edu,georgetown.edu,gmu.edu,gsu.edu,gwu.edu,harvard.edu,hawaii.edu,hbs.edu,iastate.edu,iit.edu,illinois.edu,indiana.edu,iu.edu,jhu.edu,k-state.edu,kent.edu,ku.edu,lamar.edu,liberty.edu,losrios.edu,lsu.edu,luc.edu,maine.edu,maricopa.edu,mass.edu,miami.edu,miamioh.edu,missouri.edu,mit.edu,mnscu.edu,monash.edu,msu.edu,mtu.edu,nau.edu,ncsu.edu,nd.edu,neu.edu,njit.edu,northeastern.edu,northwestern.edu,nova.edu,nyu.edu,odu.edu,ohio-state.edu,ohio.edu,okstate.edu,oregonstate.edu,osu.edu,ou.edu,ox.ac.uk,pdx.edu,pearson.com,phoenix.edu,pitt.edu,princeton.edu,psu.edu,purdue.edu,regis.edu,rice.edu,rit.edu,rochester.edu,rpi.edu,rutgers.edu,sc.edu,scu.edu,sdsu.edu,seattleu.edu,sfsu.edu,si.edu,sjsu.edu,snhu.edu,stanford.edu,stonybrook.edu,suny.edu,syr.edu,tamu.edu,temple.edu,towson.edu,ttu.edu,tufts.edu,ua.edu,uark.edu,ub.edu,uc.edu,uccs.edu,ucdavis.edu,ucf.edu,uchicago.edu,uci.edu,ucla.edu,uconn.edu,ucr.edu,ucsb.edu,ucsc.edu,ucsd.edu,ucsf.edu,udel.edu,udemy.com,ufl.edu,uga.edu,uh.edu,uic.edu,uillinois.edu,uiowa.edu,uiuc.edu,uky.edu,umass.edu,umb.edu,umbc.edu,umd.edu,umich.edu,umn.edu,umuc.edu,unc.edu,uncc.edu,unf.edu,uniminuto.edu,universityofcalifornia.edu,unl.edu,unlv.edu,unm.edu,unt.edu,uoc.edu,uoregon.edu,upc.edu,upenn.edu,upi.edu,uri.edu,usc.edu,usf.edu,usg.edu,usu.edu,uta.edu,utah.edu,utdallas.edu,utexas.edu,utk.edu,uvm.edu,uw.edu,uwm.edu,vanderbilt.edu,vccs.edu,vcu.edu,virginia.edu,vt.edu,waldenu.edu,washington.edu,wayne.edu,wednet.edu,wgu.edu,wisc.edu,wisconsin.edu,wm.edu,wmich.edu,wsu.edu,wustl.edu,wvu.edu,yale.edu', - 'college' ], - [ 'collegeboard.com,collegeconfidential.com,collegeview.com,ecollege.com,finaid.org,find-colleges-now.com,ratemyprofessors.com,ratemyteachers.com,studentsreview.com', - 'college' ], - [ 'actstudent.org,adaptedmind.com,aesoponline.com,archives.com,bibme.org,blackboard.com,bookrags.com,cengage.com,chegg.com,classdojo.com,classzone.com,cliffsnotes.com,coursecompass.com,educationconnection.com,educationdynamics.com,ets.org,familysearch.org,fastweb.com,genealogy.com,gradesaver.com,instructure.com,khanacademy.org,learn4good.com,mathway.com,mathxl.com,mcgraw-hill.com,merriam-webster.com,mheducation.com,niche.com,openstudy.com,pearsoned.com,pearsonmylabandmastering.com,pearsonsuccessnet.com,poptropica.com,powerschool.com,proprofs.com,purplemath.com,quizlet.com,readwritethink.org,renlearn.com,rhymezone.com,schoolloop.com,schoology.com,smithsonianmag.com,sparknotes.com,study.com,studyisland.com,studymode.com,synonym.com,teacherprobs.com,teacherspayteachers.com,tutorvista.com,vocabulary.com,yourschoolmatch.com', - 'education' ], - [ 'browardschools.com,k12.ca.us,k12.fl.us,k12.ga.us,k12.in.us,k12.mn.us,k12.mo.us,k12.nc.us,k12.nj.us,k12.oh.us,k12.va.us,k12.wi.us', - 'education' ], - [ 'coolmath-games.com,coolmath.com,coolmath4kids.com,coolquiz.com,funbrain.com,funtrivia.com,gamesforthebrain.com,girlsgogames.com,hoodamath.com,lumosity.com,math.com,mathsisfun.com,trivia.com,wizard101.com', - 'learning games' ], - [ 'askmen.com,boredomtherapy.com,buzzfeed.com,complex.com,dailymotion.com,elitedaily.com,gawker.com,howstuffworks.com,instagram.com,madamenoire.com,polygon.com,ranker.com,rollingstone.com,ted.com,theblaze.com,thechive.com,thecrux.com,thedailybeast.com,thoughtcatalog.com,uproxx.com,upworthy.com,zergnet.com', - 'entertainment' ], - [ '11points.com,7gid.com,adultswim.com,break.com,cheezburger.com,collegehumor.com,cracked.com,dailydawdle.com,damnlol.com,dumb.com,dumblaws.com,ebaumsworld.com,explosm.net,failblog.org,fun-gallery.com,funnygig.com,funnyjunk.com,funnymama.com,funnyordie.com,funnytear.com,funplus.com,glassgiant.com,goingviralposts.com,gorillamask.net,i-am-bored.com,icanhascheezburger.com,ifunny.com,imjussayin.co,inherentlyfunny.com,izismile.com,jokes.com,keenspot.com,knowyourmeme.com,laughstub.com,memebase.com,mememaker.net,metacafe.com,mylol.com,picslist.com,punoftheday.com,queendom.com,rajnikantvscidjokes.in,regretfulmorning.com,shareonfb.com,somethingawful.com,stupidvideos.com,superfunnyimages.com,thedailywh.at,theonion.com,tosh.comedycentral.com,uberhumor.com,welltimedphotos.com', - 'humor' ], - [ 'air.tv,amctheatres.com,boxofficemojo.com,cinapalace.com,cinaplay.com,cinemablend.com,cinemark.com,cinematical.com,collider.com,comicbookmovie.com,comingsoon.net,crackle.com,denofgeek.us,dreamworks.com,empireonline.com,enstarz.com,fandango.com,filmschoolrejects.com,flickeringmyth.com,flixster.com,fullmovie2k.com,g2g.fm,galleryhip.com,hollywood.com,hollywoodreporter.com,iglomovies.com,imdb.com,indiewire.com,instantwatcher.com,joblo.com,kickass.to,kissdrama.net,marcustheatres.com,megashare9.com,moviefone.com,movieinsider.com,moviemistakes.com,moviepilot.com,movierulz.com,movies.com,movies.yahoo.com,movieseum.com,movietickets.com,movieweb.com,mrmovietimes.com,mymovieshub.com,netflix.com,onlinemovies.pro,pelis24.com,projectfreetv.ch,redbox.com,regmovies.com,repelis.tv,rogerebert.suntimes.com,ropeofsilicon.com,rottentomatoes.com,sidereel.com,slashfilm.com,solarmovie.is,starwars.com,superherohype.com,tcm.com,twomovies.us,variety.com,vimeo.com,viooz.ac,warnerbros.com,watchfree.to,wbredirect.com,youtubeonfire.com,zmovie.tw,zumvo.com', - 'movie' ], - [ '1079ishot.com,2dopeboyz.com,8tracks.com,acdc.com,allaccess.com,allhiphop.com,allmusic.com,audiofanzine.com,audiomack.com,azlyrics.com,baeblemusic.com,bandsintown.com,billboard.com,brooklynvegan.com,brunomars.com,buzznet.com,cmt.com,coachella.com,consequenceofsound.net,contactmusic.com,countryweekly.com,dangerousminds.net,datpiff.com,ddotomen.com,diffuser.fm,directlyrics.com,djbooth.net,eventful.com,fireflyfestival.com,genius.com,guitartricks.com,harmony-central.com,hiphopdx.com,hiphopearly.com,hypem.com,idolator.com,iheart.com,jambase.com,kanyetothe.com,knue.com,lamusica.com,last.fm,livemixtapes.com,loudwire.com,lyricinterpretations.com,lyrics.net,lyricsbox.com,lyricsmania.com,lyricsmode.com,metal-archives.com,metrolyrics.com,mp3.com,mtv.co.uk,myspace.com,newnownext.com,noisetrade.com,okayplayer.com,pandora.com,phish.com,pigeonsandplanes.com,pitchfork.com,popcrush.com,radio.com,rap-up.com,rdio.com,reverbnation.com,revolvermag.com,rockhall.com,saavn.com,songlyrics.com,soundcloud.com,spin.com,spinrilla.com,spotify.com,stereogum.com,stereotude.com,talkbass.com,tasteofcountry.com,thebacklot.com,theboombox.com,theboot.com,thissongissick.com,tunesbaby.com,ultimate-guitar.com,ultimateclassicrock.com,vevo.com,vibe.com,vladtv.com,whosampled.com,wikibit.me,worldstarhiphop.com,wyrk.com,xxlmag.com', - 'music' ], - [ 'aceshowbiz.com,aintitcoolnews.com,allkpop.com,askkissy.com,atraf.co.il,audioboom.com,beamly.com,beyondhollywood.com,blastr.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,buzzsugar.com,cambio.com,celebdirtylaundry.com,celebrity-gossip.net,celebuzz.com,chisms.net,comicsalliance.com,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,dlisted.com,egotastic.com,empirenews.net,enelbrasero.com,eonline.com,etonline.com,ew.com,extratv.com,facade.com,famousfix.com,fanaru.com,fanpop.com,fansshare.com,fhm.com,geektyrant.com,glamourpage.com,gossipcenter.com,gossipcop.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodlife.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.buzznet.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,laineygossip.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,nationalenquirer.com,necolebitchie.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,okmagazine.com,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popsugar.com,purepeople.com,radaronline.com,rantchic.com,realitytea.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screencrush.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,socialitelife.com,sourcefednews.com,splitsider.com,starcasm.net,starmagazine.com,starpulse.com,straightfromthea.com,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,thehollywoodgossip.com,theresacaputo.com,thesuperficial.com,thezooom.com,tmz.com,tvnotas.com.mx,twanatells.com,usmagazine.com,vanityfair.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com', - 'entertainment news' ], - [ 'abc.go.com,abcfamily.go.com,abclocal.go.com,accesshollywood.com,aetv.com,amctv.com,animalplanet.com,bbcamerica.com,bet.com,biography.com,bravotv.com,cartoonnetwork.com,cbn.com,cbs.com,cc.com,centrictv.com,cinemax.com,comedycentral.com,ctv.ca,cwtv.com,daytondailynews.com,drphil.com,dsc.discovery.com,fox.com,fox23.com,fox4news.com,fxnetworks.com,hbo.com,history.com,hulu.com,ifc.com,iqiyi.com,jeopardy.com,kfor.com,logotv.com,mtv.com,myfoxchicago.com,myfoxdc.com,myfoxmemphis.com,myfoxphilly.com,nbc.com,nbcchicago.com,oxygen.com,pbs.org,pbskids.org,rachaelrayshow.com,rtve.es,scifi.com,sho.com,showtimeanytime.com,spike.com,sundance.tv,syfy.com,tbs.com,teamcoco.com,telemundo.com,thedoctorstv.com,titantv.com,tlc.com,tlc.discovery.com,tnt.tv,tntdrama.com,tv.com,tvguide.com,tvseriesfinale.com,usanetwork.com,uvidi.com,vh1.com,viki.com,watchcartoononline.com,watchseries-online.ch,wetv.com,wheeloffortune.com,whio.com,wnep.com,wral.com,wtvr.com,xfinitytv.com,yidio.com', - 'TV show' ], - [ 'americanhiking.org,appalachiantrail.org,canadiangeographic.ca,defenders.org,discovermagazine.com,discoveroutdoors.com,dsc.discovery.com,earthtouchnews.com,edf.org,epa.gov,ewg.org,fishngame.org,foe.org,fs.fed.us,geography.about.com,landtrustalliance.org,nationalgeographic.com,nature.com,nrdc.org,nwf.org,outdoorchannel.com,outdoors.org,seedmagazine.com,trcp.org,usda.gov,worldwildlife.org', - 'environment' ], - [ 'abbreviations.com,abcmouse.com,abcya.com,achieve3000.com,ancestry.com,animaljam.com,babble.com,babycenter.com,babynamespedia.com,behindthename.com,bestmomstv.com,brainyquote.com,cafemom.com,citationmachine.net,clubpenguin.com,cutemunchkins.com,discoveryeducation.com,disney.com,easybib.com,education.com,enotes.com,everydayfamily.com,familyeducation.com,gamefaqs.com,greatschools.org,hrw.com,imvu.com,infoplease.com,itsybitsysteps.com,justmommies.com,k12.com,kidsactivitiesblog.com,mathwarehouse.com,mom.me,mom365.com,mommyshorts.com,momswhothink.com,momtastic.com,monsterhigh.com,myheritage.com,nameberry.com,nickmom.com,pampers.com,parenthood.com,parenting.com,parenting.ivillage.com,parents.com,parentsociety.com,raz-kids.com,regentsprep.org,scarymommy.com,scholastic.com,shmoop.com,softschools.com,spanishdict.com,starfall.com,thebump.com,thefreedictionary.com,thenest.com,thinkbabynames.com,todaysparent.com,webkinz.com,whattoexpect.com', - 'family' ], - [ 'americangirl.com,barbie.com,barbiecollectibles.com,cartoonnetworkshop.com,chuckecheese.com,coloring.ws,disney.co.uk,disney.com.au,disney.go.com,disney.in,disneychannel-asia.com,disneyinternational.com,disneyjunior.com,disneylatino.com,disneyme.com,dltk-kids.com,dressupone.com,fantage.com,funbrainjr.com,hotwheels.com,icarly.com,kiwicrate.com,marvel.com,marvelkids.com,mattelgames.com,maxsteel.com,monkeyquest.com,nick-asia.com,nick.co.uk,nick.com,nick.tv,nickelodeon.com.au,nickjr.co.uk,nickjr.com,ninjakiwi.com,notdoppler.com,powerrangers.com,sciencekids.co.nz,search.disney.com,seventeen.com,teennick.com,theslap.com,yepi.com', - 'family' ], - [ 'alabama.gov,archives.gov,bls.gov,ca.gov,cancer.gov,cdc.gov,census.gov,cia.gov,cms.gov,commerce.gov,ct.gov,delaware.gov,dhs.gov,doi.gov,dol.gov,dot.gov,ed.gov,eftps.gov,epa.gov,fbi.gov,fda.gov,fema.gov,flhsmv.gov,ftc.gov,ga.gov,georgia.gov,gpo.gov,hhs.gov,house.gov,hud.gov,illinois.gov,in.gov,irs.gov,justice.gov,ky.gov,loc.gov,louisiana.gov,maryland.gov,mass.gov,michigan.gov,mo.gov,nih.gov,nj.gov,nps.gov,ny.gov,nyc.gov,ohio.gov,ok.gov,opm.gov,oregon.gov,pa.gov,recreation.gov,sba.gov,sc.gov,sec.gov,senate.gov,state.fl.us,state.gov,state.il.us,state.ma.us,state.mi.us,state.mn.us,state.nc.us,state.ny.us,state.oh.us,state.pa.us,studentloans.gov,telldc.com,texas.gov,tn.gov,travel.state.gov,tsa.gov,usa.gov,uscis.gov,uscourts.gov,usda.gov,usdoj.gov,usembassy.gov,usgs.gov,utah.gov,va.gov,virginia.gov,wa.gov,whitehouse.gov,wi.gov,wisconsin.gov', - 'government' ], - [ 'beachbody.com,bodybuilding.com,caloriecount.com,extremefitness.com,fitbit.com,fitday.com,fitnessmagazine.com,fitnessonline.com,fitwatch.com,livestrong.com,maxworkouts.com,mensfitness.com,menshealth.com,muscleandfitness.com,muscleandfitnesshers.com,myfitnesspal.com,shape.com,womenshealthmag.com', - 'health & fitness' ], - [ 'activebeat.com,alliancehealth.com,beyonddiet.com,caring.com,complete-health-and-happiness.com,diabeticconnect.com,doctoroz.com,everydayhealth.com,followmyhealth.com,greatist.com,health.com,healthboards.com,healthcaresource.com,healthgrades.com,healthguru.com,healthination.com,healthtap.com,helpguide.org,iherb.com,kidshealth.org,lifescript.com,lovelivehealth.com,medicaldaily.com,mercola.com,perfectorigins.com,prevention.com,qualityhealth.com,questdiagnostics.com,realself.com,sharecare.com,sparkpeople.com,spryliving.com,steadyhealth.com,symptomfind.com,ucomparehealthcare.com,vitals.com,webmd.com,weightwatchers.com,wellness.com,zocdoc.com', - 'health & wellness' ], - [ 'aetna.com,anthem.com,athenahealth.com,bcbs.com,bluecrossca.com,cigna.benefitnation.net,cigna.com,cigna.healthplan.com,ehealthcare.com,ehealthinsurance.com,empireblue.com,goldenrule.com,healthcare.gov,healthnet.com,humana-medicare.com,humana.com,kaiserpermanente.org,metlife.com,my.cigna.com,mybenefits.metlife.com,myuhc.com,uhc.com,unitedhealthcareonline.com,walterrayholt.com', - 'health insurance' ], - [ 'aafp.org,americanheart.org,apa.org,cancer.org,cancercenter.com,caremark.com,clevelandclinic.org,diabetesfree.org,drugs.com,emedicinehealth.com,express-scripts.com,familydoctor.org,goodrx.com,healthcaremagic.com,healthfinder.gov,healthline.com,ieee.org,intelihealth.com,labcorp.com,livecellresearch.com,mayoclinic.com,mayoclinic.org,md.com,medcohealth.com,medhelp.org,medicalnewstoday.com,medicare.gov,medicaresupplement.com,medicinenet.com,medscape.com,memorialhermann.org,merckmanuals.com,patient.co.uk,psychcentral.com,psychology.org,psychologytoday.com,rightdiagnosis.com,rxlist.com,socialpsychology.org,spine-health.com,who.int', - 'health & wellness' ], - [ 'aaa.com,aig.com,allianz-assistance.com,allstate.com,allstateagencies.com,amfam.com,amica.com,autoquotesdirect.com,esurance.com,farmers.com,farmersagent.com,geico.com,general-car-insurance-quotes.net,insurance.com,libertymutual.com,libertymutualgroup.com,mercuryinsurance.com,nationwide.com,progressive.com,progressiveagent.com,progressiveinsurance.com,provide-insurance.com,safeco.com,statefarm.com,thehartford.com,travelers.com,usaa.com', - 'insurance' ], - [ '101cookbooks.com,allrecipes.com,bettycrocker.com,bonappetit.com,chocolateandzucchini.com,chow.com,chowhound.chow.com,cookinglight.com,cooks.com,cooksillustrated.com,cooksrecipes.com,delish.com,eater.com,eatingwell.com,epicurious.com,food.com,foodandwine.com,foodgawker.com,foodnetwork.com,gourmet.com,grouprecipes.com,homemaderecipes.co,iheartnaptime.net,kraftfoods.com,kraftrecipes.com,myrecipes.com,opentable.com,pillsbury.com,recipe.com,recipesource.com,recipezaar.com,saveur.com,seriouseats.com,simplyrecipes.com,smittenkitchen.com,southernliving.com,supercook.com,tasteofhome.com,tastespotting.com,technicpack.net,thekitchn.com,urbanspoon.com,wonderhowto.com,yelp.com,yummly.com,zagat.com', - 'food & lifestyle' ], - [ 'aarp.org,allure.com,bustle.com,cosmopolitan.com,diply.com,eharmony.com,elle.com,glamour.com,grandascent.com,harpersbazaar.com,hellogiggles.com,instructables.com,instyle.com,marieclaire.com,match.com,mindbodygreen.com,nymag.com,okcupid.com,petco.com,photobucket.com,pof.com,rantlifestyle.com,redbookmag.com,reddit.com,sheknows.com,style.com,stylebistro.com,theilovedogssite.com,theknot.com,thescene.com,thrillist.com,vogue.com,womansday.com,youngcons.com,yourdictionary.com', - 'lifestyle' ], - [ 'apartmentratings.com,apartmenttherapy.com,architectmagazine.com,architecturaldigest.com,askthebuilder.com,bhg.com,bobvila.com,countryhome.com,countryliving.com,davesgarden.com,decor8blog.com,decorpad.com,diycozyhome.com,diyideas.com,diynetwork.com,doityourself.com,domainehome.com,dwell.com,elledecor.com,familyhandyman.com,frontdoor.com,gardenguides.com,gardenweb.com,getdecorating.com,goodhousekeeping.com,hgtv.com,hgtvgardens.com,hobbylobby.com,homeadvisor.com,homerepair.about.com,hometalk.com,hometime.com,hometips.com,housebeautiful.com,houzz.com,inhabitat.com,lonny.com,makingitlovely.com,marthastewart.com,michaels.com,myhomeideas.com,realsimple.com,remodelista.com,shanty-2-chic.com,styleathome.com,thehandmadehome.net,thehealthyhomeeconomist.com,thisoldhouse.com,traditionalhome.com,trulia.com,younghouselove.com', - 'home & lifestyle' ], - [ '10best.com,10tv.com,11alive.com,19actionnews.com,9news.com,abcnews.com,abcnews.go.com,adweek.com,ajc.com,anchorfree.us,arcamax.com,austin360.com,azcentral.com,bbc.co.uk,boston.com,bostonglobe.com,capecodonline.com,cbsnews.com,cheatsheet.com,chicagotribune.com,chron.com,citylab.com,cnn.com,csmonitor.com,dailyitem.com,dailymail.co.uk,dallasnews.com,eleconomista.es,examiner.com,fastcolabs.com,fivethirtyeight.com,foursquare.com,foxcarolina.com,foxnews.com,globalnews.ca,greatergood.com,guardian.co.uk,historynet.com,huffingtonpost.co.uk,huffingtonpost.com,ijreview.com,independent.co.uk,journal-news.com,kare11.com,kcra.com,kctv5.com,kgw.com,khou.com,king5.com,kirotv.com,kitv.com,kmbc.com,knoxnews.com,kpho.com,kptv.com,kron4.com,ksdk.com,ksl.com,ktvb.com,kvue.com,kxan.com,latimes.com,lifehack.org,littlethings.com,mailtribune.com,mic.com,mirror.co.uk,msn.com,msnbc.com,msnbc.msn.com,myfoxboston.com,nbcnews.com,nbcnewyork.com,newburyportnews.com,news.bbc.co.uk,news.yahoo.com,news12.com,newschannel10.com,newsday.com,newser.com,newsmax.com,newyorker.com,nj.com,nj1015.com,npr.org,nydailynews.com,nypost.com,nytimes.com,palmbeachpost.com,patch.com,philly.com,phys.org,poconorecord.com,prnewswire.com,rare.us,realclearworld.com,record-eagle.com,richmond.com,rt.com,salemnews.com,salon.com,sfgate.com,slate.com,statesman.com,suntimes.com,takepart.com,telegraph.co.uk,theatlantic.com,thedailystar.com,theguardian.com,theroot.com,theverge.com,time.com,timesonline.co.uk,topix.com,usatoday.com,usatoday30.usatoday.com,usnews.com,vice.com,vox.com,wane.com,washingtonpost.com,washingtontimes.com,wave3.com,wavy.com,wbaltv.com,wbir.com,wcnc.com,wdbj7.com,westernjournalism.com,wfaa.com,wfsb.com,wftv.com,wgal.com,wishtv.com,wisn.com,wistv.com,wivb.com,wkyc.com,wlwt.com,wmur.com,woodtv.com,wpxi.com,wsbtv.com,wsfa.com,wsmv.com,wsoctv.com,wthr.com,wtnh.com,wtsp.com,wwltv.com,wyff4.com,wzzm13.com', - 'news' ], - [ 'aei.org,breitbart.com,conservativetalknow.com,conservativetribune.com,dailykos.com,ddo.com,drudgereport.com,dscc.org,foreignpolicy.com,franklinprosperityreport.com,freedomworks.org,macleans.ca,mediamatters.org,militarytimes.com,nationaljournal.com,nationalreview.com,politicalwire.com,politico.com,pressrepublican.com,qpolitical.com,realclearpolitics.com,talkingpointsmemo.com,thehill.com,thenation.com,thinkprogress.org,tnr.com,worldoftanks.eu', - 'news' ], - [ 'americanscientist.org,discovermagazine.com,iflscience.com,livescience.com,nasa.gov,nationalgeographic.com,nature.com,newscientist.com,popsci.com,sciencedaily.com,sciencemag.org,sciencenews.org,scientificamerican.com,space.com,zmescience.com', - 'science' ], - [ 'accuweather.com,intellicast.com,noaa.gov,ssa.gov,theweathernetwork.com,weather.com,weather.gov,weather.yahoo.com,weatherbug.com,weatherunderground.com,weatherzone.com.au,wunderground.com,www.weather.com', - 'weather' ], - [ 'bhphotovideo.com,bigfolio.com,bigstockphoto.com,cameralabs.com,canonrumors.com,canstockphoto.com,digitalcamerareview.com,dpreview.com,expertphotography.com,gettyimages.com,icp.org,imaging-resource.com,intothedarkroom.com,istockphoto.com,nikonusa.com,photos.com,shutterstock.com,slrgear.com,the-digital-picture.com,thephotoargus.com,usa.canon.com,whatdigitalcamera.com,zenfolio.com', - 'photography' ], - [ 'abercrombie.com,ae.com,aeropostale.com,anthropologie.com,bananarepublic.com,buycostumes.com,chadwicks.com,express.com,forever21.com,freepeople.com,hm.com,hollisterco.com,jcrew.com,jessicalondon.com,kingsizemen.com,lordandtaylor.com,lulus.com,metrostyle.com,nomorerack.com,oldnavy.com,oldnavy.gap.com,polyvore.com,rackroomshoes.com,ralphlauren.com,refinery29.com,roamans.com,sammydress.com,shop.nordstrom.com,shopbop.com,topshop.com,urbanoutfitters.com,victoriassecret.com,wetseal.com,womanwithin.com', - 'shopping' ], - [ 'bizrate.com,compare99.com,coupons.com,dealtime.com,epinions.com,junglee.com,kijiji.ca,pricegrabber.com,pronto.com,redplum.com,retailmenot.com,shopping.com,shopzilla.com,smarter.com,valpak.com', - 'shopping' ], - [ '123greetings.com,1800baskets.com,1800flowers.com,americangreetings.com,birthdayexpress.com,bluemountain.com,e-cards.com,egreetings.com,florists.com,ftd.com,gifts.com,groupcard.com,harryanddavid.com,hipstercards.com,kabloom.com,personalcreations.com,proflowers.com,redenvelope.com,someecards.com', - 'flowers & gifts' ], - [ '6pm.com,alibaba.com,aliexpress.com,amazon.co.uk,amazon.com,asos.com,bathandbodyworks.com,bloomingdales.com,bradsdeals.com,buy.com,cafepress.com,circuitcity.com,clarkhoward.com,consumeraffairs.com,costco.com,cvs.com,dhgate.com,diapers.com,dillards.com,ebates.com,ebay.com,ebaystores.com,etsy.com,fingerhut.com,groupon.com,hsn.com,jcpenney.com,kmart.com,kohls.com,kroger.com,lowes.com,macys.com,menards.com,nextag.com,nordstrom.com,orientaltrading.com,overstock.com,qvc.com,racked.com,rewardsnetwork.com,samsclub.com,sears.com,sephora.com,shopathome.com,shopify.com,shopstyle.com,slickdeals.net,soap.com,staples.com,target.com,toptenreviews.com,vistaprint.com,walgreens.com,walmart.ca,walmart.com,wayfair.com,zappos.com,zazzle.com,zulily.com', - 'shopping' ], - [ 'acehardware.com,ashleyfurniture.com,bedbathandbeyond.com,brylanehome.com,casa.com,cb2.com,crateandbarrel.com,dwr.com,ethanallen.com,furniture.com,harborfreight.com,hayneedle.com,homedecorators.com,homedepot.com,ikea.com,info.ikea-usa.com,landofnod.com,pier1.com,plowhearth.com,potterybarn.com,restorationhardware.com,roomandboard.com,westelm.com,williams-sonoma.com', - 'home shopping' ], - [ 'alexandermcqueen.com,bergdorfgoodman.com,bottegaveneta.com,burberry-bluelabel.com,burberry.com,chanel.com,christianlouboutin.com,coach.com,diesel.com,dior.com,dolcegabbana.com,dolcegabbana.it,fendi.com,ferragamo.com,giorgioarmani.com,givenchy.com,gucci.com,guess.com,hermes.com,jeanpaulgaultier.com,jimmychoo.com,juicycouture.com,katespade.com,louisvuitton.com,manoloblahnik.com,marcjacobs.com,neimanmarcus.com,net-a-porter.com,paulsmith.co.uk,prada.com,robertocavalli.com,saksfifthavenue.com,toryburch.com,valentino.com,versace.com,vuitton.com,ysl.com,yslbeautyus.com', - 'luxury shopping' ], - [ 'bargainseatsonline.com,livenation.com,stubhub.com,ticketfly.com,ticketliquidator.com,ticketmaster.com,tickets.com,ticketsnow.com,ticketweb.com,vividseats.com', - 'events & tickets' ], - [ 'babiesrus.com,brothers-brick.com,etoys.com,fao.com,fisher-price.com,hasbro.com,hasbrotoyshop.com,lego.com,legoland.com,mattel.com,toys.com,toysrus.com,toystogrowon.com,toywiz.com', - 'toys & games' ], - [ 'challengegames.nfl.com,fantasy.nfl.com,fantasyfootballblog.net,fantasyfootballcafe.com,fantasyfootballnerd.com,fantasysmarts.com,fftoday.com,fftoolbox.com,football.fantasysports.yahoo.com,footballsfuture.com,mrfootball.com,officefootballpool.com,thehuddle.com', - 'fantasy football' ], - [ 'dailyjoust.com,draftday.com,draftking.com,draftkings.com,draftstreet.com,fanduel.com,realmoneyfantasyleagues.com,thedailyaudible.com', - 'fantasy sports' ], - [ 'cdmsports.com,fanball.com,fantasyguru.com,fantasynews.cbssports.com,fantasyquestions.com,fantasyrundown.com,fantasysharks.com,fantasysports.yahoo.com,fantazzle.com,fantrax.com,fleaflicker.com,junkyardjake.com,kffl.com,mockdraftcentral.com,myfantasyleague.com,rototimes.com,rotowire.com,rotoworld.com,rtsports.com,whatifsports.com', - 'fantasy sports' ], - [ 'football.about.com,football.com,footballoutsiders.com,nationalfootballpost.com,nflalumni.org,nflpa.com,nfltraderumors.co,profootballhof.com,profootballtalk.com,profootballtalk.nbcsports.com,profootballweekly.com', - 'football' ], - [ '49ers.com,atlantafalcons.com,azcardinals.com,baltimoreravens.com,bengals.com,buccaneers.com,buffalobills.com,chargers.com,chicagobears.com,clevelandbrowns.com,colts.com,dallascowboys.com,denverbroncos.com,detroitlions.com,giants.com,houstontexans.com,jaguars.com,kcchiefs.com,miamidolphins.com,neworleanssaints.com,newyorkjets.com,packers.com,panthers.com,patriots.com,philadelphiaeagles.com,raiders.com,redskins.com,seahawks.com,steelers.com,stlouisrams.com,titansonline.com,vikings.com', - 'football' ], - [ 'baseball-reference.com,baseballamerica.com,europeantour.com,golf.com,golfdigest.com,lpga.com,milb.com,minorleagueball.com,mlb.com,mlb.mlb.com,nascar.com,nba.com,ncaa.com,nhl.com,pga.com,pgatour.com,prowrestling.com,surfermag.com,surfline.com,surfshot.com,thehockeynews.com,tsn.com,ufc.com,worldgolfchampionships.com,wwe.com', - 'sports' ], - [ '247sports.com,active.com,armslist.com,basketball-reference.com,bigten.org,bleacherreport.com,bleedinggreennation.com,bloodyelbow.com,cagesideseats.com,cbssports.com,cinesport.com,collegespun.com,cricbuzz.com,crictime.com,csnphilly.com,csnwashington.com,cstv.com,eastbay.com,espn.com,espn.go.com,espncricinfo.com,espnfc.com,espnfc.us,espnradio.com,eteamz.com,fanatics.com,fansided.com,fbschedules.com,fieldandstream.com,flightclub.com,foxsports.com,givemesport.com,goduke.com,goheels.com,golfchannel.com,golfnow.com,grantland.com,grindtv.com,hoopshype.com,icc-cricket.com,imleagues.com,kentuckysportsradio.com,larrybrownsports.com,leaguelineup.com,maxpreps.com,mlbtraderumors.com,mmafighting.com,mmajunkie.com,mmamania.com,msn.foxsports.com,myscore.com,nbcsports.com,nbcsports.msnbc.com,nesn.com,rantsports.com,realclearsports.com,reserveamerica.com,rivals.com,runnersworld.com,sbnation.com,scout.com,sherdog.com,si.com,speedsociety.com,sportingnews.com,sports.yahoo.com,sportsillustrated.cnn.com,sportsmanias.com,sportsmonster.us,sportsonearth.com,stack.com,teamworkonline.com,thebiglead.com,thescore.com,trails.com,triblive.com,upickem.net,usatodayhss.com,watchcric.net,yardbarker.com', - 'sports news' ], - [ 'adidas.com,backcountry.com,backcountrygear.com,cabelas.com,champssports.com,competitivecyclist.com,dickssportinggoods.com,finishline.com,footlocker.com,ladyfootlocker.com,modells.com,motosport.com,mountaingear.com,newbalance.com,nike.com,patagonia.com,puma.com,reebok.com,sportsmansguide.com,steepandcheap.com,tgw.com,thenorthface.com', - 'sports & outdoor goods' ], - [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org', - 'technology' ], - [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com', - 'technology' ], - [ 'adobe.com,asus.com,avast.com,data.com,formstack.com,gboxapp.com,gotomeeting.com,hp.com,htc.com,ibm.com,intel.com,java.com,logme.in,mcafee.com,mcafeesecure.com,microsoftstore.com,norton.com,office.com,office365.com,opera.com,oracle.com,proboards.com,samsung.com,sourceforge.net,squarespace.com,techtarget.com,ultipro.com,uniblue.com,web.com,winzip.com', - 'technology' ], - [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,afterdawn.com,akihabaranews.com,appsrumors.com,avg.com,belkin.com,besttechinfo.com,betanews.com,botcrawl.com,breakingmuscle.com,cheap-phones.com,chip.de,chip.eu,citeworld.com,cleanpcremove.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,csoonline.com,daniweb.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dslreports.com,edugeek.net,eetimes.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,gizmodo.co.uk,globalsecurity.org,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,it168.com,itechpost.com,itpro.co.uk,jailbreaknation.com,laptoping.com,lightreading.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pchome.net,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,programming4.us,quickpwn.com,redmondpie.com,redorbit.com,safer-networking.org,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,sparkfun.com,speedguide.net,stuff.tv,symantec.com,taplikahome.com,techdailynews.net,techeblog.com,techie-buzz.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techradar.com,techshout.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,thewindowsclub.com,tomsitpro.com,trustedreviews.com,tuaw.com,tweaktown.com,unwiredview.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wp-themes.com,xml.com,zol.com.cn', - 'technology' ], - [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org', - 'Mozilla' ], - [ 'addictivetips.com,allthingsd.com,anandtech.com,androidcentral.com,androidpolice.com,arstechnica.com,bgr.com,boygeniusreport.com,cio.com,cnet.com,computerworld.com,crn.com,electronista.com,engadget.com,extremetech.com,fastcocreate.com,fastcodesign.com,fastcoexist.com,frontlinek12.com,gigaom.com,gizmag.com,gizmodo.com,greenbot.com,howtogeek.com,idigitaltimes.com,imore.com,informationweek.com,infoworld.com,itworld.com,kioskea.net,laptopmag.com,leadpages.net,lifehacker.com,mashable.com,networkworld.com,news.cnet.com,nwc.com,pastebin.com,pcadvisor.co.uk,pcmag.com,pcworld.com,phonearena.com,reviewed.com,serverfault.com,siteadvisor.com,slashdot.org,techcrunch.com,techdirt.com,techhive.com,technewsworld.com,techrepublic.com,techweb.com,tomsguide.com,tomshardware.com,ubergizmo.com,venturebeat.com,wired.com,xda-developers.com,zdnet.com', - 'technology news' ], - [ 'bestbuy.ca,bestbuy.com,cdw.com,compusa.com,computerlivehelp.co,cyberguys.com,dell.com,digitalinsight.com,directron.com,ebuyer.com,frontierpc.com,frys-electronics-ads.com,frys.com,geeks.com,gyazo.com,homestead.com,lenovo.com,macmall.com,microcenter.com,miniinthebox.com,mwave.com,newegg.com,officedepot.com,outletpc.com,outpost.com,radioshack.com,rakuten.com,tigerdirect.com', - 'tech retail' ], - [ 'chat.com,fring.com,hello.firefox.com,oovoo.com,viber.com', - 'video chat' ], - [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,l10n.mozilla.org,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,speckyboy.com,stackoverflow.com,status.modern.ie,validator.w3.org,w3.org,webreference.com,whatcanidoformozilla.org', - 'web development' ], - [ 'classroom.google.com,codeacademy.org,codecademy.com,codeschool.com,codeyear.com,elearning.ut.ac.id,how-to-build-websites.com,htmlcodetutorial.com,htmldog.com,htmlplayground.com,learn.jquery.com,quackit.com,roseindia.net,teamtreehouse.com,tizag.com,tutorialspoint.com,udacity.com,w3schools.com,webdevelopersnotes.com', - 'webdev education' ], - [ 'att.com,att.net,attonlineoffers.com,bell.ca,bellsouth.com,cableone.net,cablevision.com,centurylink.com,centurylink.net,centurylinkquote.com,charter-business.com,charter.com,charter.net,chartercabledeals.com,chartermedia.com,comcast.com,comcast.net,cox.com,cox.net,coxnewsweb.com,directv.com,dish.com,dishnetwork.com,freeconferencecall.com,frontier.com,hughesnet.com,liveitwithcharter.com,mycenturylink.com,mydish.com,net10.com,officialtvstream.com.es,optimum.com,optimum.net,paygonline.com,paytm.com,qwest.com,rcn.com,rebtel.com,ringcentral.com,straighttalkbyop.com,swappa.com,textem.net,timewarner.com,timewarnercable.com,tracfone.com,verizon.com,verizon.net,voipo.com,vonagebusiness.com,wayport.net,whistleout.com,wildblue.net,windstream.net,windstreambusiness.net,wowway.com,ww2.cox.com,xfinity.com', - 'telecommunication' ], - [ 'alltel.com,assurancewireless.com,attsavings.com,boostmobile.com,boostmobilestore.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,motorola.com,mycricket.com,myfamilymobile.com,nextel.com,nokia.com,nokiausa.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,sprintpcs.com,straighttalk.com,t-mobile.co.uk,t-mobile.com,tmobile.com,tracfonewireless.com,uscellular.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vodaphone.co.uk,vonange.com,vzwshop.com,wireless.att.com', - 'mobile carrier' ], - [ 'aa.com,aerlingus.com,airasia.com,aircanada.com,airfrance.com,airindia.com,alaskaair.com,alaskaairlines.com,allegiantair.com,britishairways.com,cathaypacific.com,china-airlines.com,continental.com,delta.com,deltavacations.com,dragonair.com,easyjet.com,elal.co.il,emirates.com,flightaware.com,flyfrontier.com,frontierairlines.com,hawaiianair.com,iberia.com,jetairways.com,jetblue.com,klm.com,koreanair.com,kuwait-airways.com,lan.com,lufthansa.com,malaysiaairlines.com,mihinlanka.com,nwa.com,qantas.com.au,qatarairways.com,ryanair.com,singaporeair.com,smartfares.com,southwest.com,southwestvacations.com,spiritair.com,spiritairlines.com,thaiair.com,united.com,usairways.com,virgin-atlantic.com,virginamerica.com,virginblue.com.au', - 'travel & airline' ], - [ 'carnival.com,celebrity-cruises.com,celebritycruises.com,costacruise.com,cruise.com,cruiseamerica.com,cruisecritic.com,cruisedirect.com,cruisemates.com,cruises.com,cruisesonly.com,crystalcruises.com,cunard.com,disneycruise.disney.go.com,hollandamerica.com,ncl.com,pocruises.com,princess.com,royalcaribbean.com,royalcaribbean.cruiselines.com,rssc.com,seabourn.com,silversea.com,starcruises.com,vikingrivercruises.com,windstarcruises.com', - 'travel & cruise' ], - [ 'agoda.com,airbnb.com,beaches.com,bedandbreakfast.com,bestwestern.com,booking.com,caesars.com,choicehotels.com,comfortinn.com,daysinn.com,dealbase.com,doubletree3.hilton.com,embassysuites.com,fairmont.com,flipkey.com,fourseasons.com,greatwolf.com,hamptoninn.hilton.com,hamptoninn3.hilton.com,hhonors3.hilton.com,hilton.com,hiltongardeninn3.hilton.com,hiltonworldwide.com,holidayinn.com,homeaway.com,hotelclub.com,hotelopia.com,hotels.com,hotelscombined.com,hyatt.com,ihg.com,laterooms.com,lhw.com,lq.com,mandarinoriental.com,marriott.com,motel6.com,omnihotels.com,radisson.com,ramada.com,rci.com,reservationcounter.com,resortvacationstogo.com,ritzcarlton.com,roomkey.com,sheraton.com,starwoodhotels.com,starwoodhotelshawaii.com,super8.com,thetrain.com,vacationhomerentals.com,vacationrentals.com,vrbo.com,wyndhamrewards.com', - 'hotel & resort' ], - [ 'airfarewatchdog.com,airliners.net,atlanta-airport.com,budgettravel.com,cntraveler.com,cntraveller.com,destination360.com,flightstats.com,flyertalk.com,fodors.com,frommers.com,letsgo.com,lonelyplanet.com,matadornetwork.com,perfectvacation.co,ricksteves.com,roughguides.com,timeout.com,travelalberta.us,travelandleisure.com,travelchannel.com,traveler.nationalgeographic.com,travelmath.com,traveltune.com,tripadvisor.com,vegas.com,viator.com,virtualtourist.com,wikitravel.org,worldtravelguide.net', - 'travel' ], - [ 'aavacations.com,applevacations.com,avianca.com,bookingbuddy.com,bookit.com,cheapair.com,cheapcaribbean.com,cheapflights.com,cheapoair.com,cheaptickets.com,chinahighlights.com,costcotravel.com,ctrip.com,despegar.com,edreams.net,expedia.ca,expedia.com,fareboom.com,farebuzz.com,farecast.live.com,farecompare.com,faregeek.com,flightnetwork.com,funjet.com,golastminute.com,hipmunk.com,hotwire.com,ifly.com,justairticket.com,kayak.com,lastminute.com,lastminutetravel.com,lowestfare.com,lowfares.com,momondo.com,onetime.com,onetravel.com,orbitz.com,otel.com,priceline.com,pricelinevisa.com,sidestep.com,skyscanner.com,smartertravel.com,statravel.com,tigerair.com,travelocity.com,travelonbids.com,travelzoo.com,tripsta.com,trivago.com,universalorlando.com,universalstudioshollywood.com,vacationexpress.com,venere.com,webjet.com,yatra.com', - 'travel' ], - [ 'airportrentalcars.com,alamo.com,amtrak.com,anytransitguide.com,avis.com,boltbus.com,budget.com,carrentalexpress.com,carrentals.com,coachusa.com,dollar.com,e-zrentacar.com,enterprise.com,europcar.com,foxrentacar.com,gotobus.com,greyhound.com,hertz.com,hertzondemand.com,indianrail.gov.in,irctc.co.in,megabus.com,mta.info,nationalcar.com,nationalrail.co.uk,njtransit.com,paylesscar.com,paylesscarrental.com,peterpanbus.com,raileurope.com,rentalcars.com,rideuta.com,stagecoachbus.com,thrifty.com,uber.com,wanderu.com,zipcar.com', - 'travel & transit' ], - [ 'bulbagarden.net,cheatcc.com,cheatmasters.com,cheats.ign.com,comicvine.com,computerandvideogames.com,counter-strike.net,escapistmagazine.com,gamedaily.com,gamefront.com,gameinformer.com,gamerankings.com,gamespot.com,gamesradar.com,gamestop.com,gametrailers.com,gamezone.com,giantbomb.com,ign.com,kotaku.com,metacritic.com,minecraft-server-list.com,minecraftforge.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mmo-champion.com,mojang.com,pcgamer.com,planetminecraft.com,supercheats.com,thesims.com,totaljerkface.com,unity3d.com,vg247.com,wowhead.com', - 'gaming' ], - [ 'a10.com,absolutist.com,addictinggames.com,aeriagames.com,agame.com,alpha-wars.com,arcadeyum.com,armorgames.com,ballerarcade.com,battle.net,battlefield.com,bigfishgames.com,bioware.com,bitrhymes.com,candystand.com,conjurorthegame.com,crazymonkeygames.com,crusharcade.com,curse.com,cuttherope.net,dreammining.com,dressupgames.com,ea.com,easports.com,fps-pb.com,freearcade.com,freeonlinegames.com,friv.com,funplusgame.com,gamefly.com,gameforge.com,gamehouse.com,gamejolt.com,gameloft.com,gameoapp.com,gamepedia.com,gamersfirst.com,games.com,games.yahoo.com,gamesgames.com,gamezhero.com,gamingwonderland.com,ganymede.eu,goodgamestudios.com,gpotato.com,gsn.com,guildwars2.com,hirezstudios.com,igg.com,iwin.com,kahoot.it,king.com,kizi.com,kongregate.com,leagueoflegends.com,lolking.net,maxgames.com,minecraft-mp.com,minecraft.net,miniclip.com,mmo-play.com,mmorpg.com,mobafire.com,moviestarplanet.com,myonlinearcade.com,needforspeed.com,newgrounds.com,nexusmods.com,nintendo.com,noxxic.com,onrpg.com,origin.com,pch.com,peakgames.net,playstation.com,pogo.com,pokemon.com,popcap.com,primarygames.com,r2games.com,railnation.us,riotgames.com,roblox.com,rockstargames.com,runescape.com,shockwave.com,silvergames.com,spore.com,steamcommunity.com,steampowered.com,stickpage.com,swtor.com,tetrisfriends.com,thegamerstop.com,thesims3.com,twitch.tv,warthunder.com,wildtangent.com,worldoftanks.com,worldofwarcraft.com,worldofwarplanes.com,worldofwarships.com,xbox.com,y8.com,zone.msn.com,zynga.com,zyngawithfriends.com', - 'online gaming' ], -]); - // Only allow link urls that are http(s) const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]); @@ -768,14 +596,6 @@ let DirectoryLinksProvider = { this._enhancedLinks.get(NewTabUtils.extractSite(link.url)); }, - /** - * Get the display name of an allowed frecent sites. Returns undefined for a - * unallowed frecent sites. - */ - getFrecentSitesName(sites) { - return ALLOWED_FRECENT_SITES.get(sites.join(",")); - }, - /** * Check if a url's scheme is in a Set of allowed schemes and if the base * domain is allowed. @@ -837,9 +657,8 @@ let DirectoryLinksProvider = { }.bind(this); rawLinks.suggested.filter(validityFilter).forEach((link, position) => { - // Only allow suggested links with approved frecent sites - let name = this.getFrecentSitesName(link.frecent_sites); - if (name == undefined) { + // Suggested sites must have an adgroup name. + if (!link.adgroup_name) { return; } @@ -848,7 +667,7 @@ let DirectoryLinksProvider = { ParserUtils.SanitizerDropNonCSSPresentation; link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : ""); - link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0) || name); + link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0)); link.lastVisitDate = rawLinks.suggested.length - position; // check if link wants to avoid inadjacent sites if (link.check_inadjacency) { diff --git a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js index 1cfdaaeed51a..a1f597803773 100644 --- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js +++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js @@ -67,6 +67,7 @@ let suggestedTile1 = { url: "http://turbotax.com", type: "affiliate", lastVisitDate: 4, + adgroup_name: "Adgroup1", frecent_sites: [ "taxact.com", "hrblock.com", @@ -78,6 +79,7 @@ let suggestedTile2 = { url: "http://irs.gov", type: "affiliate", lastVisitDate: 3, + adgroup_name: "Adgroup2", frecent_sites: [ "taxact.com", "hrblock.com", @@ -89,6 +91,7 @@ let suggestedTile3 = { url: "http://hrblock.com", type: "affiliate", lastVisitDate: 2, + adgroup_name: "Adgroup3", frecent_sites: [ "taxact.com", "freetaxusa.com", @@ -100,6 +103,7 @@ let suggestedTile4 = { url: "http://sponsoredtile.com", type: "sponsored", lastVisitDate: 1, + adgroup_name: "Adgroup4", frecent_sites: [ "sponsoredtarget.com" ] @@ -296,9 +300,6 @@ add_task(function test_updateSuggestedTile() { let testObserver = new TestFirstRun(); DirectoryLinksProvider.addObserver(testObserver); - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; - yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); @@ -413,16 +414,12 @@ add_task(function test_updateSuggestedTile() { // Cleanup yield promiseCleanDirectoryLinksProvider(); - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; }); add_task(function test_suggestedLinksMap() { - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => "testing map"; - let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]}; let dataURI = 'data:application/json,' + JSON.stringify(data); @@ -452,20 +449,16 @@ add_task(function test_suggestedLinksMap() { let suggestedLinksItr = suggestedLinks.values(); for (let link of expected_data[site]) { let linkCopy = JSON.parse(JSON.stringify(link)); - linkCopy.targetedName = "testing map"; + linkCopy.targetedName = link.adgroup_name; linkCopy.explanation = ""; isIdentical(suggestedLinksItr.next().value, linkCopy); } }) yield promiseCleanDirectoryLinksProvider(); - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; }); add_task(function test_topSitesWithSuggestedLinks() { - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; - let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"]; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = function(site) { @@ -513,7 +506,6 @@ add_task(function test_topSitesWithSuggestedLinks() { isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); // Cleanup. - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; }); @@ -530,13 +522,15 @@ add_task(function test_suggestedAttributes() { let title = "the title"; let type = "affiliate"; let url = "http://test.url/"; + let adgroup_name = "Mozilla"; let data = { suggested: [{ frecent_sites, imageURI, title, type, - url + url, + adgroup_name }] }; let dataURI = "data:application/json," + escape(JSON.stringify(data)); @@ -575,8 +569,6 @@ add_task(function test_suggestedAttributes() { add_task(function test_frequencyCappedSites_views() { Services.prefs.setCharPref(kPingUrlPref, ""); - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; @@ -590,7 +582,8 @@ add_task(function test_frequencyCappedSites_views() { type: "affiliate", frecent_sites: targets, url: testUrl, - frequency_caps: {daily: 5} + frequency_caps: {daily: 5}, + adgroup_name: "Test" }], directory: [{ type: "organic", @@ -644,7 +637,6 @@ add_task(function test_frequencyCappedSites_views() { checkFirstTypeAndLength("organic", 1); // Cleanup. - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); @@ -654,8 +646,6 @@ add_task(function test_frequencyCappedSites_views() { add_task(function test_frequencyCappedSites_click() { Services.prefs.setCharPref(kPingUrlPref, ""); - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; @@ -668,7 +658,8 @@ add_task(function test_frequencyCappedSites_click() { suggested: [{ type: "affiliate", frecent_sites: targets, - url: testUrl + url: testUrl, + adgroup_name: "Test" }], directory: [{ type: "organic", @@ -716,7 +707,6 @@ add_task(function test_frequencyCappedSites_click() { checkFirstTypeAndLength("organic", 1); // Cleanup. - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); @@ -1217,8 +1207,6 @@ add_task(function test_DirectoryLinksProvider_getEnhancedLink() { }); add_task(function test_DirectoryLinksProvider_enhancedURIs() { - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount; @@ -1226,7 +1214,7 @@ add_task(function test_DirectoryLinksProvider_enhancedURIs() { let data = { "suggested": [ - {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", frecent_sites: ["test.com"]} + {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", adgroup_name: "Test", frecent_sites: ["test.com"]} ], "directory": [ {url: "http://example.net", enhancedImageURI: "data:,net2", title:"DirectoryTitle"} @@ -1262,7 +1250,6 @@ add_task(function test_DirectoryLinksProvider_enhancedURIs() { do_check_eq(links[0].enhancedImageURI, "data:,net1"); // Cleanup. - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); @@ -1312,9 +1299,6 @@ add_task(function test_timeSensetiveSuggestedTiles() { let testObserver = new TestTimingRun(); DirectoryLinksProvider.addObserver(testObserver); - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; - yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); @@ -1424,7 +1408,6 @@ add_task(function test_timeSensetiveSuggestedTiles() { // Cleanup yield promiseCleanDirectoryLinksProvider(); - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; @@ -1728,10 +1711,6 @@ add_task(function test_DirectoryLinksProvider_anonymous() { add_task(function test_sanitizeExplanation() { // Note: this is a basic test to ensure we applied sanitization to the link explanation. // Full testing for appropriate sanitization is done in parser/xml/test/unit/test_sanitizer.js. - - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => "testing map"; - let data = {"suggested": [suggestedTile5]}; let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data)); @@ -1760,9 +1739,6 @@ add_task(function test_inadjecentSites() { let testObserver = new TestFirstRun(); DirectoryLinksProvider.addObserver(testObserver); - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; - yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); @@ -1918,7 +1894,6 @@ add_task(function test_inadjecentSites() { // Cleanup gLinks.removeProvider(DirectoryLinksProvider); DirectoryLinksProvider._inadjacentSitesUrl = origInadjacentSitesUrl; - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; @@ -1926,8 +1901,6 @@ add_task(function test_inadjecentSites() { }); add_task(function test_reportPastImpressions() { - let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; - DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount; @@ -1939,7 +1912,8 @@ add_task(function test_reportPastImpressions() { suggested: [{ type: "affiliate", frecent_sites: targets, - url: testUrl + url: testUrl, + adgroup_name: "Test" }] }; let dataURI = "data:application/json," + JSON.stringify(data); @@ -2026,7 +2000,6 @@ add_task(function test_reportPastImpressions() { // Cleanup. done = true; - DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; }); diff --git a/browser/themes/shared/devtools/performance.css b/browser/themes/shared/devtools/performance.css index 0936bad442a8..4279a16f64ec 100644 --- a/browser/themes/shared/devtools/performance.css +++ b/browser/themes/shared/devtools/performance.css @@ -589,6 +589,14 @@ menuitem.marker-color-graphs-grey:before, min-width: 200px; } +#optimizations-graph { + height: 30px; +} + +#jit-optimizations-view.empty #optimizations-graph { + display: none !important; +} + /* override default styles for tree widget */ #jit-optimizations-view .tree-widget-empty-text { font-size: inherit; diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 2d5c55facf35..6dbd83d4a70c 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -52,13 +52,13 @@ } #nav-bar[brighttext] { - --toolbarbutton-hover-background: rgba(255,255,255,.1); - --toolbarbutton-hover-bordercolor: rgba(255,255,255,.1); + --toolbarbutton-hover-background: rgba(255,255,255,.15); + --toolbarbutton-hover-bordercolor: rgba(255,255,255,.15); --toolbarbutton-hover-boxshadow: none; - --toolbarbutton-active-background: rgba(255,255,255,.15); - --toolbarbutton-active-bordercolor: rgba(255,255,255,.15); - --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.15) inset; + --toolbarbutton-active-background: rgba(255,255,255,.3); + --toolbarbutton-active-bordercolor: rgba(255,255,255,.3); + --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset; } #menubar-items { @@ -842,8 +842,8 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba } #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon { - padding-top: 8px; - padding-bottom: 8px; + padding-top: calc(var(--toolbarbutton-vertical-inner-padding) + 6px); + padding-bottom: calc(var(--toolbarbutton-vertical-inner-padding) + 6px); } #nav-bar .toolbaritem-combined-buttons { diff --git a/dom/apps/AndroidUtils.jsm b/dom/apps/AndroidUtils.jsm new file mode 100644 index 000000000000..53fb4002cf93 --- /dev/null +++ b/dom/apps/AndroidUtils.jsm @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { interfaces: Ci, utils: Cu } = Components; + +this.EXPORTED_SYMBOLS = ["AndroidUtils"]; + +Cu.import("resource://gre/modules/AppsUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Messaging", + "resource://gre/modules/Messaging.jsm"); + +let appsRegistry = null; + +function debug() { + //dump("-*- AndroidUtils " + Array.slice(arguments) + "\n"); +} + +// Helper functions to manage Android native apps. We keep them in the +// registry with a `kind` equals to "android-native" and we also store +// the package name and class name in the registry. +// Communication with the android side happens through json messages. + +this.AndroidUtils = { + init: function(aRegistry) { + appsRegistry = aRegistry; + Services.obs.addObserver(this, "Android:Apps:Installed", false); + Services.obs.addObserver(this, "Android:Apps:Uninstalled", false); + }, + + uninit: function() { + Services.obs.removeObserver(this, "Android:Apps:Installed"); + Services.obs.removeObserver(this, "Android:Apps:Uninstalled"); + }, + + getOriginAndManifestURL: function(aPackageName) { + let origin = "android://" + aPackageName.toLowerCase(); + let manifestURL = origin + "/manifest.webapp"; + return [origin, manifestURL]; + }, + + getPackageAndClassFromManifestURL: function(aManifestURL) { + debug("getPackageAndClassFromManifestURL " + aManifestURL); + let app = appsRegistry.getAppByManifestURL(aManifestURL); + if (!app) { + debug("No app for " + aManifestURL); + return []; + } + return [app.android_packagename, app.android_classname]; + }, + + buildAndroidAppData: function(aApp) { + // Use the package and class name to get a unique origin. + // We put the version with the normal case as part of the manifest url. + let [origin, manifestURL] = + this.getOriginAndManifestURL(aApp.packagename); + // TODO: Bug 1204557 to improve the icons support. + let manifest = { + name: aApp.name, + icons: { "96": aApp.icon } + } + debug("Origin is " + origin); + let appData = { + app: { + installOrigin: origin, + origin: origin, + manifest: manifest, + manifestURL: manifestURL, + manifestHash: AppsUtils.computeHash(JSON.stringify(manifest)), + appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED, + removable: aApp.removable, + android_packagename: aApp.packagename, + android_classname: aApp.classname + }, + isBrowser: false, + isPackage: false + }; + + return appData; + }, + + installAndroidApps: function() { + return Messaging.sendRequestForResult({ type: "Apps:GetList" }).then( + aApps => { + debug("Got " + aApps.apps.length + " android apps."); + let promises = []; + aApps.apps.forEach(app => { + debug("App is " + app.name + " removable? " + app.removable); + let p = new Promise((aResolveInstall, aRejectInstall) => { + let appData = this.buildAndroidAppData(app); + appsRegistry.confirmInstall(appData, null, aResolveInstall); + }); + promises.push(p); + }); + + // Wait for all apps to be installed. + return Promise.all(promises); + } + ).then(appsRegistry._saveApps.bind(appsRegistry)); + }, + + observe: function(aSubject, aTopic, aData) { + let data; + try { + data = JSON.parse(aData); + } catch(e) { + debug(e); + return; + } + + if (aTopic == "Android:Apps:Installed") { + let appData = this.buildAndroidAppData(data); + appsRegistry.confirmInstall(appData); + } else if (aTopic == "Android:Apps:Uninstalled") { + let [origin, manifestURL] = + this.getOriginAndManifestURL(data.packagename); + appsRegistry.uninstall(manifestURL); + } + }, +} diff --git a/dom/apps/AppsUtils.jsm b/dom/apps/AppsUtils.jsm index f5d3bd12c27f..b2f9b6bc3dac 100644 --- a/dom/apps/AppsUtils.jsm +++ b/dom/apps/AppsUtils.jsm @@ -130,6 +130,10 @@ function _setAppProperties(aObj, aApp) { aObj.kind = aApp.kind; aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true; aObj.sideloaded = aApp.sideloaded; +#ifdef MOZ_B2GDROID + aObj.android_packagename = aApp.android_packagename; + aObj.android_classname = aApp.android_classname; +#endif } this.AppsUtils = { diff --git a/dom/apps/Webapps.jsm b/dom/apps/Webapps.jsm index e80d579d2204..4f3c4a14c51f 100644 --- a/dom/apps/Webapps.jsm +++ b/dom/apps/Webapps.jsm @@ -91,6 +91,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ImportExport", XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Messaging", + "resource://gre/modules/Messaging.jsm"); + #ifdef MOZ_WIDGET_GONK XPCOMUtils.defineLazyGetter(this, "libcutils", function() { Cu.import("resource://gre/modules/systemlibs.js"); @@ -195,6 +198,7 @@ this.DOMApplicationRegistry = { get kPackaged() "packaged", get kHosted() "hosted", get kHostedAppcache() "hosted-appcache", + get kAndroid() "android-native", // Path to the webapps.json file where we store the registry data. appsFile: null, @@ -259,6 +263,11 @@ this.DOMApplicationRegistry = { this.getFullAppByManifestURL.bind(this)); MessageBroadcaster.init(this.getAppByManifestURL); + + if (AppConstants.MOZ_B2GDROID) { + Cu.import("resource://gre/modules/AndroidUtils.jsm"); + AndroidUtils.init(this); + } }, // loads the current registry, that could be empty on first run. @@ -464,7 +473,9 @@ this.DOMApplicationRegistry = { }), appKind: function(aApp, aManifest) { - if (aApp.origin.startsWith("app://")) { + if (aApp.origin.startsWith("android://")) { + return this.kAndroid; + } if (aApp.origin.startsWith("app://")) { return this.kPackaged; } else { // Hosted apps, can be appcached or not. @@ -522,7 +533,10 @@ this.DOMApplicationRegistry = { // Installs a 3rd party app. installPreinstalledApp: function installPreinstalledApp(aId) { -#ifdef MOZ_WIDGET_GONK + if (!AppConstants.MOZ_B2GDROID && AppConstants.platform !== "gonk") { + return false; + } + // In some cases, the app might be already installed under a different ID but // with the same manifestURL. In that case, the only content of the webapp will // be the id of the old version, which is the one we'll keep. @@ -625,7 +639,6 @@ this.DOMApplicationRegistry = { zipReader.close(); } return isPreinstalled; -#endif }, // For hosted apps, uninstall an app served from http:// if we have @@ -792,6 +805,10 @@ this.DOMApplicationRegistry = { yield this.installSystemApps(); } + if (AppConstants.MOZ_B2GDROID) { + yield AndroidUtils.installAndroidApps(); + } + // At first run, install preloaded apps and set up their permissions. for (let id in this.webapps) { let isPreinstalled = this.installPreinstalledApp(id); @@ -804,7 +821,7 @@ this.DOMApplicationRegistry = { } // Need to update the persisted list of apps since // installPreinstalledApp() removes the ones failing to install. - this._saveApps(); + yield this._saveApps(); Services.prefs.setBoolPref("dom.apps.reset-permissions", true); } @@ -1195,6 +1212,9 @@ this.DOMApplicationRegistry = { Services.obs.removeObserver(this, "xpcom-shutdown"); cpmm = null; ppmm = null; + if (AppConstants.MOZ_B2GDROID) { + AndroidUtils.uninit(); + } } else if (aTopic == "memory-pressure") { // Clear the manifest cache on memory pressure. this._manifestCache = {}; @@ -1304,22 +1324,22 @@ this.DOMApplicationRegistry = { this.registryReady.then( () => { switch (aMessage.name) { case "Webapps:Install": { -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg)); -#else - this.doInstall(msg, mm); -#endif + if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) { + Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg)); + } else { + this.doInstall(msg, mm); + } break; } case "Webapps:GetSelf": this.getSelf(msg, mm); break; case "Webapps:Uninstall": -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg)); -#else - this.doUninstall(msg, mm); -#endif + if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) { + Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg)); + } else { + this.doUninstall(msg, mm); + } break; case "Webapps:Launch": this.doLaunch(msg, mm); @@ -1337,11 +1357,11 @@ this.DOMApplicationRegistry = { this.getNotInstalled(msg, mm); break; case "Webapps:InstallPackage": { -#ifdef MOZ_WIDGET_ANDROID - Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg)); -#else - this.doInstallPackage(msg, mm); -#endif + if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) { + Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg)); + } else { + this.doInstallPackage(msg, mm); + } break; } case "Webapps:Download": @@ -1602,6 +1622,19 @@ this.DOMApplicationRegistry = { return; } + // Delegate native android apps launch. + if (this.kAndroid == app.kind) { + debug("Launching android app " + app.origin); + let [packageName, className] = + AndroidUtils.getPackageAndClassFromManifestURL(aManifestURL); + debug(" " + packageName + " " + className); + Messaging.sendRequest({ type: "Apps:Launch", + packagename: packageName, + classname: className }); + aOnSuccess(); + return; + } + // We have to clone the app object as nsIDOMApplication objects are // stringified as an empty object. (see bug 830376) let appClone = AppsUtils.cloneAppObject(app); @@ -2046,8 +2079,9 @@ this.DOMApplicationRegistry = { } // If the app is packaged and its manifestURL has an app:// scheme, - // then we can't have an update. - if (app.kind == this.kPackaged && app.manifestURL.startsWith("app://")) { + // or if it's a native Android app then we can't have an update. + if (app.kind == this.kAndroid || + (app.kind == this.kPackaged && app.manifestURL.startsWith("app://"))) { sendError("NOT_UPDATABLE"); return; } @@ -2779,8 +2813,10 @@ this.DOMApplicationRegistry = { _setupApp: function(aData, aId) { let app = aData.app; - // app can be uninstalled - app.removable = true; + // app can be uninstalled by default. + if (app.removable === undefined) { + app.removable = true; + } if (aData.isPackage) { // Override the origin with the correct id. @@ -2813,7 +2849,8 @@ this.DOMApplicationRegistry = { appObject.downloading = true; appObject.downloadSize = aLocaleManifest.size; appObject.readyToApplyDownload = false; - } else if (appObject.kind == this.kHosted) { + } else if (appObject.kind == this.kHosted || + appObject.kind == this.kAndroid) { appObject.installState = "installed"; appObject.downloadAvailable = false; appObject.downloading = false; @@ -2869,8 +2906,6 @@ this.DOMApplicationRegistry = { app.appStatus = AppsUtils.getAppManifestStatus(aManifest); - app.removable = true; - // Reuse the app ID if the scheme is "app". let uri = Services.io.newURI(app.origin, null, null); if (uri.scheme == "app") { @@ -2960,6 +2995,7 @@ this.DOMApplicationRegistry = { let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId); this.webapps[id] = appObject; + this._manifestCache[id] = jsonManifest; // For package apps, the permissions are not in the mini-manifest, so // don't update the permissions yet. @@ -4064,12 +4100,24 @@ this.DOMApplicationRegistry = { try { aData.app = yield this._getAppWithManifest(aData.manifestURL); - let prefName = "dom.mozApps.auto_confirm_uninstall"; - if (Services.prefs.prefHasUserValue(prefName) && - Services.prefs.getBoolPref(prefName)) { - yield this._uninstallApp(aData.app); + if (this.kAndroid == aData.app.kind) { + debug("Uninstalling android app " + aData.app.origin); + let [packageName, className] = + AndroidUtils.getPackageAndClassFromManifestURL(aData.manifestURL); + Messaging.sendRequest({ type: "Apps:Uninstall", + packagename: packageName, + classname: className }); + // We have to wait for Android's uninstall before sending the + // uninstall event, so fake an error here. + response = "Webapps:Uninstall:Return:KO"; } else { - yield this._promptForUninstall(aData); + let prefName = "dom.mozApps.auto_confirm_uninstall"; + if (Services.prefs.prefHasUserValue(prefName) && + Services.prefs.getBoolPref(prefName)) { + yield this._uninstallApp(aData.app); + } else { + yield this._promptForUninstall(aData); + } } } catch (error) { aData.error = error; diff --git a/dom/apps/moz.build b/dom/apps/moz.build index 5b730f8d4649..419ad9a45fd1 100644 --- a/dom/apps/moz.build +++ b/dom/apps/moz.build @@ -42,6 +42,11 @@ EXTRA_JS_MODULES += [ 'UserCustomizations.jsm', ] +if CONFIG['MOZ_B2GDROID']: + EXTRA_JS_MODULES += [ + 'AndroidUtils.jsm', + ] + EXTRA_PP_JS_MODULES += [ 'AppsUtils.jsm', 'ImportExport.jsm', diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index 580bfe6553a7..cca80dfb79f3 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -23,14 +23,14 @@ namespace dom { class ThreadSafeChromeUtils { public: - // Implemented in toolkit/devtools/server/HeapSnapshot.cpp + // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp static void SaveHeapSnapshot(GlobalObject& global, JSContext* cx, - const nsAString& filePath, const HeapSnapshotBoundaries& boundaries, + nsAString& filePath, ErrorResult& rv); - // Implemented in toolkit/devtools/server/HeapSnapshot.cpp + // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp static already_AddRefed ReadHeapSnapshot(GlobalObject& global, JSContext* cx, const nsAString& filePath, diff --git a/dom/system/gonk/MozMtpDatabase.cpp b/dom/system/gonk/MozMtpDatabase.cpp index d4d7af2837bb..039039ca2357 100644 --- a/dom/system/gonk/MozMtpDatabase.cpp +++ b/dom/system/gonk/MozMtpDatabase.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include using namespace android; using namespace mozilla; @@ -63,6 +65,7 @@ ObjectPropertyAsStr(MtpObjectProperty aProperty) case MTP_PROPERTY_PROTECTION_STATUS: return "MTP_PROPERTY_PROTECTION_STATUS"; case MTP_PROPERTY_OBJECT_SIZE: return "MTP_PROPERTY_OBJECT_SIZE"; case MTP_PROPERTY_OBJECT_FILE_NAME: return "MTP_PROPERTY_OBJECT_FILE_NAME"; + case MTP_PROPERTY_DATE_CREATED: return "MTP_PROPERTY_DATE_CREATED"; case MTP_PROPERTY_DATE_MODIFIED: return "MTP_PROPERTY_DATE_MODIFIED"; case MTP_PROPERTY_PARENT_OBJECT: return "MTP_PROPERTY_PARENT_OBJECT"; case MTP_PROPERTY_PERSISTENT_UID: return "MTP_PROPERTY_PERSISTENT_UID"; @@ -76,6 +79,16 @@ ObjectPropertyAsStr(MtpObjectProperty aProperty) return "MTP_PROPERTY_???"; } +static char* +FormatDate(time_t aTime, char *aDateStr, size_t aDateStrSize) +{ + struct tm tm; + localtime_r(&aTime, &tm); + MTP_LOG("(%ld) tm_zone = %s off = %ld", aTime, tm.tm_zone, tm.tm_gmtoff); + strftime(aDateStr, aDateStrSize, "%Y%m%dT%H%M%S", &tm); + return aDateStr; +} + MozMtpDatabase::MozMtpDatabase() : mMutex("MozMtpDatabase::mMutex"), mDb(mMutex), @@ -222,9 +235,21 @@ MozMtpDatabase::UpdateEntry(MtpObjectHandle aHandle, DeviceStorageFile* aFile) int64_t fileSize = 0; aFile->mFile->GetFileSize(&fileSize); entry->mObjectSize = fileSize; - aFile->mFile->GetLastModifiedTime(&entry->mDateCreated); - entry->mDateModified = entry->mDateCreated; - MTP_DBG("UpdateEntry (0x%08x file %s)", entry->mHandle, entry->mPath.get()); + + PRTime dateModifiedMsecs; + // GetLastModifiedTime returns msecs + aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs); + entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC; + entry->mDateCreated = entry->mDateModified; + entry->mDateAdded = entry->mDateModified; + + #if USE_DEBUG + char dateStr[20]; + MTP_DBG("UpdateEntry (0x%08x file %s) modified (%ld) %s", + entry->mHandle, entry->mPath.get(), + entry->mDateModified, + FormatDate(entry->mDateModified, dateStr, sizeof(dateStr))); + #endif } @@ -454,14 +479,19 @@ MozMtpDatabase::CreateEntryForFileAndNotify(const nsACString& aPath, aFile->mFile->GetFileSize(&fileSize); entry->mObjectSize = fileSize; - aFile->mFile->GetLastModifiedTime(&entry->mDateCreated); + // Note: Even though PRTime records usec, GetLastModifiedTime returns + // msecs. + PRTime dateModifiedMsecs; + aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs); + entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC; } else { // Found a slash, this makes this a directory component entry->mObjectFormat = MTP_FORMAT_ASSOCIATION; entry->mObjectSize = 0; - entry->mDateCreated = PR_Now(); + time(&entry->mDateModified); } - entry->mDateModified = entry->mDateCreated; + entry->mDateCreated = entry->mDateModified; + entry->mDateAdded = entry->mDateModified; AddEntryAndNotify(entry, aMtpServer); MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entry->mHandle, entry->mPath.get()); @@ -503,8 +533,11 @@ MozMtpDatabase::AddDirectory(MtpStorageID aStorageID, entry->mObjectName = dirEntry->name; entry->mDisplayName = dirEntry->name; entry->mPath = filename; - entry->mDateCreated = fileInfo.creationTime; - entry->mDateModified = fileInfo.modifyTime; + + // PR_GetFileInfo64 returns timestamps in usecs + entry->mDateModified = fileInfo.modifyTime / PR_USEC_PER_SEC; + entry->mDateCreated = fileInfo.creationTime / PR_USEC_PER_SEC; + time(&entry->mDateAdded); if (fileInfo.type == PR_FILE_FILE) { entry->mObjectFormat = MTP_FORMAT_DEFINED; @@ -653,9 +686,71 @@ MozMtpDatabase::beginSendObject(const char* aPath, entry->mObjectFormat = aFormat; entry->mObjectSize = aSize; + if (aModified != 0) { + // Currently, due to the way that parseDateTime is coded in + // frameworks/av/media/mtp/MtpUtils.cpp, aModified winds up being the number + // of seconds from the epoch in local time, rather than UTC time. So we + // need to convert it back to being relative to UTC since that's what linux + // expects time_t to contain. + // + // In more concrete testable terms, if the host parses 2015-08-02 02:22:00 + // as a local time in the Pacific timezone, aModified will come to us as + // 1438482120. + // + // What we want is what mktime would pass us with the same date. Using python + // (because its simple) with the current timezone set to be America/Vancouver: + // + // >>> import time + // >>> time.mktime((2015, 8, 2, 2, 22, 0, 0, 0, -1)) + // 1438507320.0 + // >>> time.localtime(1438507320) + // time.struct_time(tm_year=2015, tm_mon=8, tm_mday=2, tm_hour=2, tm_min=22, tm_sec=0, tm_wday=6, tm_yday=214, tm_isdst=1) + // + // Currently, when a file has a modification time of 2015-08-22 02:22:00 PDT + // then aModified will come in as 1438482120 which corresponds to + // 2015-08-22 02:22:00 UTC + + struct tm tm; + if (gmtime_r(&aModified, &tm) != NULL) { + // GMT always comes back with tm_isdst = 0, so we set it to -1 in order + // to have mktime figure out dst based on the date. + tm.tm_isdst = -1; + aModified = mktime(&tm); + if (aModified == (time_t)-1) { + aModified = 0; + } + } else { + aModified = 0; + } + } + if (aModified == 0) { + // The ubuntu host doesn't pass in the modified/created times in the + // SENDOBJECT packet, so aModified winds up being zero. About the best + // we can do with that is to use the current time. + time(&aModified); + } + + // And just an FYI for anybody else looking at timestamps. Under OSX you + // need to use the Android File Transfer program to copy files into the + // phone. That utility passes in both date modified and date created + // timestamps, but they're both equal to the time that the file was copied + // and not the times that are associated with the files. + + // Now we have aModified in a traditional time_t format, which is the number + // of seconds from the UTC epoch. + + entry->mDateModified = aModified; + entry->mDateCreated = entry->mDateModified; + entry->mDateAdded = entry->mDateModified; + AddEntry(entry); - MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s'", entry->mHandle, aParent, aPath); + #if USE_DEBUG + char dateStr[20]; + MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s' aModified %ld %s", + entry->mHandle, aParent, aPath, aModified, + FormatDate(entry->mDateModified, dateStr, sizeof(dateStr))); + #endif mBeginSendObjectCalled = true; return entry->mHandle; @@ -677,6 +772,22 @@ MozMtpDatabase::endSendObject(const char* aPath, if (aSucceeded) { RefPtr entry = GetEntry(aHandle); if (entry) { + // The android MTP server only copies the data in, it doesn't set the + // modified timestamp, so we do that here. + + struct utimbuf new_times; + struct stat sb; + + char dateStr[20]; + MTP_LOG("Path: '%s' setting modified time to (%ld) %s", + entry->mPath.get(), entry->mDateModified, + FormatDate(entry->mDateModified, dateStr, sizeof(dateStr))); + + stat(entry->mPath.get(), &sb); + new_times.actime = sb.st_atime; // Preserve atime + new_times.modtime = entry->mDateModified; + utime(entry->mPath.get(), &new_times); + FileWatcherNotify(entry, "modified"); } } else { @@ -794,6 +905,7 @@ static const MtpObjectProperty sSupportedObjectProperties[] = MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, // just the filename - no directory MTP_PROPERTY_NAME, + MTP_PROPERTY_DATE_CREATED, MTP_PROPERTY_DATE_MODIFIED, MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, @@ -874,6 +986,7 @@ GetTypeOfObjectProp(MtpObjectProperty aProperty) {MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 }, {MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 }, {MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR }, + {MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR }, {MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR }, {MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 }, {MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR }, @@ -1130,6 +1243,8 @@ MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle, UnprotectedDbArray::size_type numEntries = result.Length(); UnprotectedDbArray::index_type entryIdx; + char dateStr[20]; + aPacket.putUInt32(numObjectProperties * numEntries); for (entryIdx = 0; entryIdx < numEntries; entryIdx++) { RefPtr entry = result[entryIdx]; @@ -1178,23 +1293,24 @@ MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle, aPacket.putUInt16(0); // 0 = No Protection break; + case MTP_PROPERTY_DATE_CREATED: { + aPacket.putUInt16(MTP_TYPE_STR); + aPacket.putString(FormatDate(entry->mDateCreated, dateStr, sizeof(dateStr))); + MTP_LOG("mDateCreated: (%ld) %s", entry->mDateCreated, dateStr); + break; + } + case MTP_PROPERTY_DATE_MODIFIED: { aPacket.putUInt16(MTP_TYPE_STR); - PRExplodedTime explodedTime; - PR_ExplodeTime(entry->mDateModified, PR_LocalTimeParameters, &explodedTime); - char dateStr[20]; - PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime); - aPacket.putString(dateStr); + aPacket.putString(FormatDate(entry->mDateModified, dateStr, sizeof(dateStr))); + MTP_LOG("mDateModified: (%ld) %s", entry->mDateModified, dateStr); break; } case MTP_PROPERTY_DATE_ADDED: { aPacket.putUInt16(MTP_TYPE_STR); - PRExplodedTime explodedTime; - PR_ExplodeTime(entry->mDateCreated, PR_LocalTimeParameters, &explodedTime); - char dateStr[20]; - PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime); - aPacket.putString(dateStr); + aPacket.putString(FormatDate(entry->mDateAdded, dateStr, sizeof(dateStr))); + MTP_LOG("mDateAdded: (%ld) %s", entry->mDateAdded, dateStr); break; } @@ -1243,11 +1359,14 @@ MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle, aInfo.mAssociationDesc = 0; aInfo.mSequenceNumber = 0; aInfo.mName = ::strdup(entry->mObjectName.get()); + aInfo.mDateCreated = entry->mDateCreated; + aInfo.mDateModified = entry->mDateModified; + + MTP_LOG("aInfo.mDateCreated = %ld entry->mDateCreated = %ld", + aInfo.mDateCreated, entry->mDateCreated); + MTP_LOG("aInfo.mDateModified = %ld entry->mDateModified = %ld", + aInfo.mDateModified, entry->mDateModified); - // entry->mDateXxxx is a PRTime stores the time as microseconds from the epoch. - // aInfo.mDateXxxx is time_t which stores the time as seconds from the epoch. - aInfo.mDateCreated = entry->mDateCreated / PR_USEC_PER_SEC; - aInfo.mDateModified = entry->mDateModified / PR_USEC_PER_SEC; aInfo.mKeywords = ::strdup("fxos,touch"); return MTP_RESPONSE_OK; @@ -1383,6 +1502,7 @@ MozMtpDatabase::getObjectPropertyDesc(MtpObjectProperty aProperty, case MTP_PROPERTY_OBJECT_FILE_NAME: result = new MtpProperty(aProperty, MTP_TYPE_STR, true); break; + case MTP_PROPERTY_DATE_CREATED: case MTP_PROPERTY_DATE_MODIFIED: case MTP_PROPERTY_DATE_ADDED: result = new MtpProperty(aProperty, MTP_TYPE_STR); diff --git a/dom/system/gonk/MozMtpDatabase.h b/dom/system/gonk/MozMtpDatabase.h index c25d44aa73cc..bb46d9e35785 100644 --- a/dom/system/gonk/MozMtpDatabase.h +++ b/dom/system/gonk/MozMtpDatabase.h @@ -133,7 +133,8 @@ private: mParent(0), mObjectSize(0), mDateCreated(0), - mDateModified(0) {} + mDateModified(0), + mDateAdded(0) {} NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DbEntry) @@ -145,8 +146,9 @@ private: uint64_t mObjectSize; nsCString mDisplayName; nsCString mPath; - PRTime mDateCreated; - PRTime mDateModified; + time_t mDateCreated; + time_t mDateModified; + time_t mDateAdded; protected: ~DbEntry() {} diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 3c6ac273ae1a..01ccb4a8cd76 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -14460,7 +14460,16 @@ ICCContactHelperObject.prototype = { this.updateContactField(pbr, contact, field, (fieldEntry) => { contactField = Object.assign(contactField, fieldEntry); updateField.call(this); - }, onerror); + }, (errorMsg) => { + // Bug 1194149, there are some sim cards without sufficient + // Type 2 USIM contact fields record. We allow user continue + // importing contacts. + if (errorMsg === CONTACT_ERR_NO_FREE_RECORD_FOUND) { + updateField.call(this); + return; + } + onerror(errorMsg); + }); }).call(this); }, diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js index 8b26ee0c2855..2e39d64aae0a 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js @@ -606,6 +606,111 @@ add_test(function test_update_icc_contact() { run_next_test(); }); +/** + * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM and + * insufficient space to store Type 2 USIM contact fields. + */ +add_test(function test_update_icc_contact_full_email_and_anr_field() { + const ADN_RECORD_ID = 100; + const ADN_SFI = 1; + const IAP_FILE_ID = 0x4f17; + const EMAIL_FILE_ID = 0x4f50; + const EMAIL_RECORD_ID = 20; + const ANR0_FILE_ID = 0x4f11; + const ANR0_RECORD_ID = 30; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + let ril = context.RIL; + + function do_test(aSimType, aContactType, aContact, aPin2) { + ril.appType = CARD_APPTYPE_USIM; + ril.iccInfoPrivate.sst = [0x2, 0x0, 0x0, 0x0, 0x0]; + + recordHelper.readPBR = function(onsuccess, onerror) { + onsuccess([{ + adn: {fileId: ICC_EF_ADN, + sfi: ADN_SFI}, + iap: {fileId: IAP_FILE_ID}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 0}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 1} + }]); + }; + + recordHelper.updateADNLike = function(fileId, contact, pin2, onsuccess, onerror) { + if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) { + equal(fileId, ICC_EF_ADN); + } + equal(pin2, aPin2); + equal(contact.alphaId, aContact.alphaId); + equal(contact.number, aContact.number); + onsuccess({alphaId: contact.alphaId, + number: contact.number}); + }; + + recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess([0xff, 0xff]); + }; + + recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess(); + }; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + let recordId = 0; + // emulate email and anr don't have free record. + if (fileId === EMAIL_FILE_ID || fileId === ANR0_FILE_ID) { + onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); + } else { + onsuccess(recordId); + } + }; + + let isSuccess = false; + let onsuccess = function onsuccess(updatedContact) { + equal(ADN_RECORD_ID, updatedContact.recordId); + equal(aContact.alphaId, updatedContact.alphaId); + equal(updatedContact.email, null); + equal(updatedContact.anr, null); + + do_print("updateICCContact success"); + isSuccess = true; + }; + + let onerror = function onerror(errorMsg) { + do_print("updateICCContact failed: " + errorMsg); + }; + + contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror); + ok(isSuccess); + } + + let contact = { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test", + number: "123456", + email: "test@mail.com", + anr: ["+654321"] + }; + + // USIM + do_print("Test update USIM adn contacts"); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null); + + run_next_test(); +}); + /** * Verify updateICCContact with removal of anr and email with File Type 1. */ diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp index 4ca27cab41d7..3a4a967d17e5 100644 --- a/dom/telephony/Telephony.cpp +++ b/dom/telephony/Telephony.cpp @@ -62,8 +62,13 @@ public: }; Telephony::Telephony(nsPIDOMWindow* aOwner) - : DOMEventTargetHelper(aOwner) + : DOMEventTargetHelper(aOwner), + mAudioAgentNotify(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY), + mIsAudioStartPlaying(false), + mHaveDispatchedInterruptBeginEvent(false), + mMuted(AudioChannelService::IsAudioChannelMutedByDefault()) { + MOZ_ASSERT(aOwner); nsCOMPtr global = do_QueryInterface(aOwner); MOZ_ASSERT(global); @@ -518,6 +523,73 @@ Telephony::StopTone(const Optional& aServiceId, ErrorResult& aRv) aRv = mService->StopTone(serviceId); } +void +Telephony::OwnAudioChannel(ErrorResult& aRv) +{ + if (mAudioAgent) { + return; + } + + mAudioAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); + MOZ_ASSERT(mAudioAgent); + aRv = mAudioAgent->Init(GetParentObject(), + (int32_t)AudioChannel::Telephony, this); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + aRv = HandleAudioAgentState(); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} + +nsresult +Telephony::HandleAudioAgentState() +{ + if (!mAudioAgent) { + return NS_OK; + } + + Nullable activeCall; + GetActive(activeCall); + nsresult rv; + // Only stop the agent when there's no call. + if ((!mCalls.Length() && !mGroup->CallsArray().Length()) && + mIsAudioStartPlaying) { + mIsAudioStartPlaying = false; + rv = mAudioAgent->NotifyStoppedPlaying(mAudioAgentNotify); + mAudioAgent = nullptr; + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else if (!activeCall.IsNull() && !mIsAudioStartPlaying) { + mIsAudioStartPlaying = true; + float volume; + bool muted; + rv = mAudioAgent->NotifyStartedPlaying(mAudioAgentNotify, &volume, &muted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // In B2G, the system app manages audio playback policy. If there is a new + // sound want to be playback, it must wait for the permission from the + // system app. It means that the sound would be muted first, and then be + // unmuted. For telephony, the behaviors are hold() first, then resume(). + // However, the telephony service can't handle all these requests within a + // short period. The telephony service would reject our resume request, + // because the modem have not changed the call state yet. It causes that + // the telephony can't be resumed. Therefore, we don't mute the telephony + // at the beginning. + volume = 1.0; + muted = false; + rv = WindowVolumeChanged(volume, muted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + return NS_OK; +} + bool Telephony::GetMuted(ErrorResult& aRv) const { @@ -591,13 +663,77 @@ Telephony::GetReady(ErrorResult& aRv) const return promise.forget(); } +// nsIAudioChannelAgentCallback + +NS_IMETHODIMP +Telephony::WindowVolumeChanged(float aVolume, bool aMuted) +{ + // It's impossible to put all the calls on-hold in the multi-call case. + if (mCalls.Length() > 1 || + (mCalls.Length() == 1 && mGroup->CallsArray().Length())) { + return NS_ERROR_FAILURE; + } + + ErrorResult rv; + nsCOMPtr global = do_QueryInterface(GetOwner()); + nsRefPtr promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + bool isSingleCall = mCalls.Length(); + nsCOMPtr callback = new TelephonyCallback(promise); + if (isSingleCall) { + rv = aMuted ? mCalls[0]->Hold(callback) : mCalls[0]->Resume(callback); + } else { + rv = aMuted ? mGroup->Hold(callback) : mGroup->Resume(callback); + } + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // These events will be triggered when the telephony is interrupted by other + // audio channel. + if (mMuted != aMuted) { + mMuted = aMuted; + // We should not dispatch "mozinterruptend" when the system app initializes + // the telephony audio from muted to unmuted at the first time. The event + // "mozinterruptend" must be dispatched after the "mozinterruptbegin". + if (!mHaveDispatchedInterruptBeginEvent && mMuted) { + DispatchTrustedEvent(NS_LITERAL_STRING("mozinterruptbegin")); + mHaveDispatchedInterruptBeginEvent = mMuted; + } else if (mHaveDispatchedInterruptBeginEvent && !mMuted) { + DispatchTrustedEvent(NS_LITERAL_STRING("mozinterruptend")); + mHaveDispatchedInterruptBeginEvent = mMuted; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Telephony::WindowAudioCaptureChanged() +{ + // Do nothing, it's useless for the telephony object. + return NS_OK; +} + // nsITelephonyListener NS_IMETHODIMP Telephony::CallStateChanged(uint32_t aLength, nsITelephonyCallInfo** aAllInfo) { + nsresult rv; for (uint32_t i = 0; i < aLength; ++i) { - HandleCallInfo(aAllInfo[i]); + rv = HandleCallInfo(aAllInfo[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = HandleAudioAgentState(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } return NS_OK; } @@ -633,6 +769,7 @@ Telephony::EnumerateCallStateComplete() mGroup->ChangeState(callState); } + HandleAudioAgentState(); if (mReadyPromise) { mReadyPromise->MaybeResolve(JS::UndefinedHandleValue); } diff --git a/dom/telephony/Telephony.h b/dom/telephony/Telephony.h index 0ef8ce27704d..241fbf9746c1 100644 --- a/dom/telephony/Telephony.h +++ b/dom/telephony/Telephony.h @@ -7,6 +7,8 @@ #ifndef mozilla_dom_telephony_telephony_h__ #define mozilla_dom_telephony_telephony_h__ +#include "AudioChannelService.h" + #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/telephony/TelephonyCommon.h" @@ -31,6 +33,7 @@ class TelephonyDialCallback; class OwningTelephonyCallOrTelephonyCallGroup; class Telephony final : public DOMEventTargetHelper, + public nsIAudioChannelAgentCallback, private nsITelephonyListener { /** @@ -44,6 +47,8 @@ class Telephony final : public DOMEventTargetHelper, friend class telephony::TelephonyDialCallback; + // The audio agent is needed to communicate with the audio channel service. + nsCOMPtr mAudioAgent; nsCOMPtr mService; nsRefPtr mListener; @@ -54,8 +59,14 @@ class Telephony final : public DOMEventTargetHelper, nsRefPtr mReadyPromise; + uint32_t mAudioAgentNotify; + bool mIsAudioStartPlaying; + bool mHaveDispatchedInterruptBeginEvent; + bool mMuted; + public: NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK NS_DECL_NSITELEPHONYLISTENER NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper) NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Telephony, @@ -94,6 +105,15 @@ public: void StopTone(const Optional& aServiceId, ErrorResult& aRv); + // In the audio channel architecture, the system app needs to know the state + // of every audio channel, including the telephony. Therefore, when a + // telephony call is activated , the audio channel service would notify the + // system app about that. And we need an agent to communicate with the audio + // channel service. We would follow the call states to make a correct + // notification. + void + OwnAudioChannel(ErrorResult& aRv); + bool GetMuted(ErrorResult& aRv) const; @@ -213,6 +233,10 @@ private: nsresult HandleCallInfo(nsITelephonyCallInfo* aInfo); + + // Check the call states to decide whether need to send the notificaiton. + nsresult + HandleAudioAgentState(); }; } // namespace dom diff --git a/dom/telephony/TelephonyCall.cpp b/dom/telephony/TelephonyCall.cpp index cfe87cbb6397..aa90455f36cd 100644 --- a/dom/telephony/TelephonyCall.cpp +++ b/dom/telephony/TelephonyCall.cpp @@ -324,34 +324,11 @@ TelephonyCall::Hold(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { - NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!" - " (State: %u)", mCallState).get()); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - - if (mGroup) { - NS_WARNING("Hold a call in conference is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - - if (!mSwitchable) { - NS_WARNING("Hold a non-switchable call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); - - if (mSecondId) { - // No state transition when we switch two numbers within one TelephonyCall - // object. Otherwise, the state here will be inconsistent with the backend - // RIL and will never be right. - return promise.forget(); + aRv = Hold(callback); + if (NS_WARN_IF(aRv.Failed() && + !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) { + return nullptr; } return promise.forget(); @@ -365,28 +342,78 @@ TelephonyCall::Resume(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_HELD) { - NS_WARNING(nsPrintfCString("Resume non-held call is rejected!" + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Resume(callback); + if (NS_WARN_IF(aRv.Failed() && + !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) { + return nullptr; + } + + return promise.forget(); +} + +nsresult +TelephonyCall::Hold(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { + NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!" " (State: %u)", mCallState).get()); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (mGroup) { + NS_WARNING("Hold a call in conference is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (!mSwitchable) { + NS_WARNING("Hold a non-switchable call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + if (mSecondId) { + // No state transition when we switch two numbers within one TelephonyCall + // object. Otherwise, the state here will be inconsistent with the backend + // RIL and will never be right. + return NS_OK; + } + + return NS_OK; +} + +nsresult +TelephonyCall::Resume(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_HELD) { + NS_WARNING("Resume non-held call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } if (mGroup) { NS_WARNING("Resume a call in conference is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } if (!mSwitchable) { NS_WARNING("Resume a non-switchable call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + nsresult rv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } - return promise.forget(); -} + return NS_OK; +} \ No newline at end of file diff --git a/dom/telephony/TelephonyCall.h b/dom/telephony/TelephonyCall.h index dbb53f444bb9..0b31b447aa40 100644 --- a/dom/telephony/TelephonyCall.h +++ b/dom/telephony/TelephonyCall.h @@ -13,6 +13,8 @@ #include "mozilla/dom/TelephonyCallId.h" #include "mozilla/dom/telephony/TelephonyCommon.h" +#include "nsITelephonyService.h" + class nsPIDOMWindow; namespace mozilla { @@ -185,6 +187,12 @@ private: ~TelephonyCall(); + nsresult + Hold(nsITelephonyCallback* aCallback); + + nsresult + Resume(nsITelephonyCallback* aCallback); + void ChangeStateInternal(uint16_t aCallState, bool aFireEvents); diff --git a/dom/telephony/TelephonyCallGroup.cpp b/dom/telephony/TelephonyCallGroup.cpp index 541cc134a79b..12040b6a31d7 100644 --- a/dom/telephony/TelephonyCallGroup.cpp +++ b/dom/telephony/TelephonyCallGroup.cpp @@ -347,16 +347,13 @@ TelephonyCallGroup::Hold(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { - NS_WARNING("Holding a non-connected call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Hold(callback); + if (NS_WARN_IF(aRv.Failed() && + !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) { + return nullptr; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(), - callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); return promise.forget(); } @@ -370,15 +367,48 @@ TelephonyCallGroup::Resume(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_HELD) { - NS_WARNING("Resuming a non-held call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Resume(callback); + if (NS_WARN_IF(aRv.Failed() && + !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) { + return nullptr; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(), - callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); return promise.forget(); } + +nsresult +TelephonyCallGroup::Hold(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { + NS_WARNING("Holding a non-connected call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(), + aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +TelephonyCallGroup::Resume(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_HELD) { + NS_WARNING("Resuming a non-held call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(), + aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} diff --git a/dom/telephony/TelephonyCallGroup.h b/dom/telephony/TelephonyCallGroup.h index 38c1ba1474cd..8e473b919951 100644 --- a/dom/telephony/TelephonyCallGroup.h +++ b/dom/telephony/TelephonyCallGroup.h @@ -30,6 +30,8 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCallGroup, DOMEventTargetHelper) + friend class Telephony; + nsPIDOMWindow* GetParentObject() const { @@ -108,6 +110,12 @@ private: explicit TelephonyCallGroup(nsPIDOMWindow* aOwner); ~TelephonyCallGroup(); + nsresult + Hold(nsITelephonyCallback* aCallback); + + nsresult + Resume(nsITelephonyCallback* aCallback); + nsresult NotifyCallsChanged(TelephonyCall* aCall); diff --git a/dom/webidl/Telephony.webidl b/dom/webidl/Telephony.webidl index 5eb460b74bbc..19fd9dd80c41 100644 --- a/dom/webidl/Telephony.webidl +++ b/dom/webidl/Telephony.webidl @@ -49,6 +49,12 @@ interface Telephony : EventTarget { [Throws] void stopTone(optional unsigned long serviceId); + // Calling this method, the app will be treated as owner of the telephony + // calls from the AudioChannel policy. + [Throws, + CheckAllPermissions="audio-channel-telephony"] + void ownAudioChannel(); + [Throws] attribute boolean muted; diff --git a/dom/webidl/ThreadSafeChromeUtils.webidl b/dom/webidl/ThreadSafeChromeUtils.webidl index fa0f92fd5f5c..de1ae2661765 100644 --- a/dom/webidl/ThreadSafeChromeUtils.webidl +++ b/dom/webidl/ThreadSafeChromeUtils.webidl @@ -14,13 +14,15 @@ interface ThreadSafeChromeUtils { * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and * restricted by |boundaries|, and write it to the provided file path. * - * @param filePath The file path to write the heap snapshot to. - * * @param boundaries The portion of the heap graph to write. + * + * @returns The path to the file the heap snapshot was written + * to. This is guaranteed to be within the temp + * directory and its file name will match the regexp + * `\d+(\-\d+)?\.fxsnapshot`. */ [Throws] - static void saveHeapSnapshot(DOMString filePath, - optional HeapSnapshotBoundaries boundaries); + static DOMString saveHeapSnapshot(optional HeapSnapshotBoundaries boundaries); /** * Deserialize a core dump into a HeapSnapshot. diff --git a/dom/wifi/test/marionette/manifest.ini b/dom/wifi/test/marionette/manifest.ini index 3326a7be1b81..c2e398d037ab 100644 --- a/dom/wifi/test/marionette/manifest.ini +++ b/dom/wifi/test/marionette/manifest.ini @@ -10,8 +10,11 @@ qemu = true [test_wifi_auto_connect.js] [test_wifi_static_ip.js] [test_wifi_tethering_wifi_disabled.js] +skip-if = android_version > '15' # Bug 1203075 [test_wifi_tethering_wifi_inactive.js] +skip-if = android_version > '15' # Bug 1203075 [test_wifi_tethering_wifi_active.js] +skip-if = android_version > '15' # Bug 1203075 [test_wifi_manage_server_certificate.js] [test_wifi_manage_user_certificate.js] [test_wifi_manage_pkcs12_certificate.js] diff --git a/mobile/android/.eslintignore b/mobile/android/.eslintignore index 413086bef188..80955a6040da 100644 --- a/mobile/android/.eslintignore +++ b/mobile/android/.eslintignore @@ -21,4 +21,3 @@ modules/WebappManager.jsm # Non-standard `(catch ex if ...)` components/Snippets.js -modules/MatchstickApp.jsm diff --git a/mobile/android/b2gdroid/app/Makefile.in b/mobile/android/b2gdroid/app/Makefile.in index 5bc25c6cc31c..87dec2d0f8ba 100644 --- a/mobile/android/b2gdroid/app/Makefile.in +++ b/mobile/android/b2gdroid/app/Makefile.in @@ -5,6 +5,7 @@ ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml JAVAFILES := \ + src/main/java/org/mozilla/b2gdroid/Apps.java \ src/main/java/org/mozilla/b2gdroid/Launcher.java \ src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \ $(NULL) diff --git a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java new file mode 100644 index 000000000000..26966400405c --- /dev/null +++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.b2gdroid; + +import java.io.ByteArrayOutputStream; +import java.util.Iterator; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +import android.net.Uri; +import android.util.Base64; +import android.util.Log; + +import org.json.JSONObject; +import org.json.JSONArray; + +import org.mozilla.gecko.EventDispatcher; +import org.mozilla.gecko.util.GeckoEventListener; + +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; + +class Apps extends BroadcastReceiver + implements GeckoEventListener { + private static final String LOGTAG = "B2G:Apps"; + + private Context mContext; + + Apps(Context context) { + mContext = context; + EventDispatcher.getInstance() + .registerGeckoThreadListener(this, + "Apps:GetList", + "Apps:Launch", + "Apps:Uninstall"); + + // Observe app installation and removal. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiver(this, filter); + } + + void destroy() { + mContext.unregisterReceiver(this); + EventDispatcher.getInstance() + .unregisterGeckoThreadListener(this, + "Apps:GetList", + "Apps:Launch", + "Apps:Uninstall"); + } + + JSONObject activityInfoToJson(ActivityInfo info, PackageManager pm) { + JSONObject obj = new JSONObject(); + try { + obj.put("name", info.loadLabel(pm).toString()); + obj.put("packagename", info.packageName); + obj.put("classname", info.name); + + final ApplicationInfo appInfo = info.applicationInfo; + // Pre-installed apps can't be uninstalled. + final boolean removable = + (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0; + + obj.put("removable", removable); + + // For now, create a data: url for the icon, since we need additional + // android:// protocol support for icons. Once it's there we'll do + // something like: obj.put("icon", "android:icon/" + info.packageName); + Drawable d = pm.getApplicationIcon(info.packageName); + Bitmap bitmap = ((BitmapDrawable)d).getBitmap(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + byte[] byteArray = byteArrayOutputStream.toByteArray(); + String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT); + obj.put("icon", "data:image/png;base64," + encoded); + } catch(Exception ex) { + Log.wtf(LOGTAG, "Error building ActivityInfo JSON", ex); + } + return obj; + } + + public void handleMessage(String event, JSONObject message) { + Log.w(LOGTAG, "Received " + event); + + if ("Apps:GetList".equals(event)) { + JSONObject ret = new JSONObject(); + JSONArray array = new JSONArray(); + PackageManager pm = mContext.getPackageManager(); + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + final Iterator i = pm.queryIntentActivities(mainIntent, 0).iterator(); + try { + while (i.hasNext()) { + ActivityInfo info = i.next().activityInfo; + array.put(activityInfoToJson(info, pm)); + } + ret.put("apps", array); + } catch(Exception ex) { + Log.wtf(LOGTAG, "error, making list of apps", ex); + } + EventDispatcher.sendResponse(message, ret); + } else if ("Apps:Launch".equals(event)) { + try { + String className = message.getString("classname"); + String packageName = message.getString("packagename"); + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setClassName(packageName, className); + mContext.startActivity(intent); + } catch(Exception ex) { + Log.wtf(LOGTAG, "Error launching app", ex); + } + } else if ("Apps:Uninstall".equals(event)) { + try { + String packageName = message.getString("packagename"); + Uri packageUri = Uri.parse("package:" + packageName); + final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); + mContext.startActivity(intent); + } catch(Exception ex) { + Log.wtf(LOGTAG, "Error uninstalling app", ex); + } + } + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(LOGTAG, intent.getAction() + " " + intent.getDataString()); + + String packageName = intent.getDataString().substring(8); + String action = intent.getAction(); + if ("android.intent.action.PACKAGE_ADDED".equals(action)) { + PackageManager pm = mContext.getPackageManager(); + Intent launch = pm.getLaunchIntentForPackage(packageName); + if (launch == null) { + Log.d(LOGTAG, "No launchable intent for " + packageName); + return; + } + ActivityInfo info = launch.resolveActivityInfo(pm, 0); + + JSONObject obj = activityInfoToJson(info, pm); + GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Installed", obj.toString()); + GeckoAppShell.sendEventToGecko(e); + } else if ("android.intent.action.PACKAGE_REMOVED".equals(action)) { + JSONObject obj = new JSONObject(); + try { + obj.put("packagename", packageName); + } catch(Exception ex) { + Log.wtf(LOGTAG, "Error building PACKAGE_REMOVED JSON", ex); + } + GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Uninstalled", obj.toString()); + GeckoAppShell.sendEventToGecko(e); + } + } +} diff --git a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java index 6bfc9fd21e87..c14bcba2698e 100644 --- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java +++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java @@ -4,9 +4,6 @@ package org.mozilla.b2gdroid; -import java.io.ByteArrayOutputStream; -import java.util.Iterator; - import android.app.Activity; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -14,24 +11,12 @@ import android.app.KeyguardManager.KeyguardLock; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; import android.os.Bundle; -import android.util.Base64; import android.util.Log; import android.view.View; -import android.widget.ImageView; import org.json.JSONObject; -import org.json.JSONArray; -import org.json.JSONException; import org.mozilla.gecko.BaseGeckoInterface; import org.mozilla.gecko.ContactService; @@ -45,6 +30,7 @@ import org.mozilla.gecko.IntentHelper; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.b2gdroid.ScreenStateObserver; +import org.mozilla.b2gdroid.Apps; public class Launcher extends Activity implements GeckoEventListener, ContextGetter { @@ -52,6 +38,7 @@ public class Launcher extends Activity private ContactService mContactService; private ScreenStateObserver mScreenStateObserver; + private Apps mApps; /** ContextGetter */ public Context getContext() { @@ -68,6 +55,7 @@ public class Launcher extends Activity GeckoBatteryManager.getInstance().start(this); mContactService = new ContactService(EventDispatcher.getInstance(), this); + mApps = new Apps(this); } private void hideSplashScreen() { @@ -123,6 +111,7 @@ public class Launcher extends Activity "Launcher:Ready"); mContactService.destroy(); + mApps.destroy(); } @Override diff --git a/mobile/android/base/resources/drawable-hdpi/menu_light.png b/mobile/android/base/resources/drawable-hdpi/menu_light.png deleted file mode 100644 index ec0857ffd21e..000000000000 Binary files a/mobile/android/base/resources/drawable-hdpi/menu_light.png and /dev/null differ diff --git a/mobile/android/base/resources/drawable-xhdpi/menu_light.png b/mobile/android/base/resources/drawable-xhdpi/menu_light.png deleted file mode 100644 index b944b3351d42..000000000000 Binary files a/mobile/android/base/resources/drawable-xhdpi/menu_light.png and /dev/null differ diff --git a/mobile/android/base/resources/values-v11/styles.xml b/mobile/android/base/resources/values-v11/styles.xml index c89ee32d8ce2..82fbd4801a51 100644 --- a/mobile/android/base/resources/values-v11/styles.xml +++ b/mobile/android/base/resources/values-v11/styles.xml @@ -98,9 +98,12 @@ diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml index cd3c96d61f2f..6290a97a703f 100644 --- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -730,7 +730,8 @@ diff --git a/mobile/android/chrome/content/.eslintrc b/mobile/android/chrome/content/.eslintrc index 949e1312f8f1..32513189aec0 100644 --- a/mobile/android/chrome/content/.eslintrc +++ b/mobile/android/chrome/content/.eslintrc @@ -10,7 +10,6 @@ globals: SimpleServiceDiscovery: false TabMirror: false MediaPlayerApp: false - MatchstickApp: false RokuApp: false SearchEngines: false ConsoleAPI: true diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js index 4ce515c1643d..2252d78e5fa0 100644 --- a/mobile/android/chrome/content/CastingApps.js +++ b/mobile/android/chrome/content/CastingApps.js @@ -20,20 +20,6 @@ var rokuDevice = { extensions: ["mp4"] }; -var matchstickDevice = { - id: "matchstick:dial", - target: "urn:dial-multiscreen-org:service:dial:1", - filters: { - manufacturer: "openflint" - }, - factory: function(aService) { - Cu.import("resource://gre/modules/MatchstickApp.jsm"); - return new MatchstickApp(aService); - }, - types: ["video/mp4", "video/webm"], - extensions: ["mp4", "webm"] -}; - var mediaPlayerDevice = { id: "media:router", target: "media:router", @@ -85,7 +71,6 @@ var CastingApps = { // Register targets SimpleServiceDiscovery.registerDevice(rokuDevice); - SimpleServiceDiscovery.registerDevice(matchstickDevice); // MediaPlayerDevice will notify us any time the native device list changes. mediaPlayerDevice.init(); diff --git a/mobile/android/modules/MatchstickApp.jsm b/mobile/android/modules/MatchstickApp.jsm deleted file mode 100644 index 80e543a296d4..000000000000 --- a/mobile/android/modules/MatchstickApp.jsm +++ /dev/null @@ -1,375 +0,0 @@ -/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["MatchstickApp"]; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/Services.jsm"); - -function log(msg) { - Services.console.logStringMessage(msg); -} - -const MATCHSTICK_PLAYER_URL = "http://openflint.github.io/flint-player/player.html"; - -const STATUS_RETRY_COUNT = 5; // Number of times we retry a partial status -const STATUS_RETRY_WAIT = 1000; // Delay between attempts in milliseconds - -/* MatchstickApp is a wrapper for interacting with a DIAL server. - * The basic interactions all use a REST API. - * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs - */ -function MatchstickApp(aServer) { - this.server = aServer; - this.app = "~flintplayer"; - this.resourceURL = this.server.appsURL + this.app; - this.token = null; - this.statusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this.statusRetry = 0; -} - -MatchstickApp.prototype = { - status: function status(aCallback) { - // Query the server to see if an application is already running - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - xhr.open("GET", this.resourceURL, true); - xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - xhr.setRequestHeader("Accept", "application/xml; charset=utf8"); - xhr.setRequestHeader("Authorization", this.token); - - xhr.addEventListener("load", (function() { - if (xhr.status == 200) { - let doc = xhr.responseXML; - let state = doc.querySelector("state").textContent; - - // The serviceURL can be missing if the player is not completely loaded - let serviceURL = null; - let serviceNode = doc.querySelector("channelBaseUrl"); - if (serviceNode) { - serviceURL = serviceNode.textContent + "/senders/" + this.token; - } - - if (aCallback) - aCallback({ state: state, serviceURL: serviceURL }); - } else { - if (aCallback) - aCallback({ state: "error" }); - } - }).bind(this), false); - - xhr.addEventListener("error", (function() { - if (aCallback) - aCallback({ state: "error" }); - }).bind(this), false); - - xhr.send(null); - }, - - start: function start(aCallback) { - // Start a given app with any extra query data. Each app uses it's own data scheme. - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - xhr.open("POST", this.resourceURL, true); - xhr.overrideMimeType("text/xml"); - xhr.setRequestHeader("Content-Type", "application/json"); - - xhr.addEventListener("load", (function() { - if (xhr.status == 200 || xhr.status == 201) { - this.statusRetry = 0; - - let response = JSON.parse(xhr.responseText); - this.token = response.token; - this.pingInterval = response.interval; - - if (aCallback) - aCallback(true); - } else { - if (aCallback) - aCallback(false); - } - }).bind(this), false); - - xhr.addEventListener("error", (function() { - if (aCallback) - aCallback(false); - }).bind(this), false); - - let data = { - type: "launch", - app_info: { - url: MATCHSTICK_PLAYER_URL, - useIpc: true, - maxInactive: -1 - } - }; - - xhr.send(JSON.stringify(data)); - }, - - stop: function stop(aCallback) { - // Send command to kill an app, if it's already running. - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - xhr.open("DELETE", this.resourceURL + "/run", true); - xhr.overrideMimeType("text/plain"); - xhr.setRequestHeader("Accept", "application/xml; charset=utf8"); - xhr.setRequestHeader("Authorization", this.token); - - xhr.addEventListener("load", (function() { - if (xhr.status == 200) { - if (aCallback) - aCallback(true); - } else { - if (aCallback) - aCallback(false); - } - }).bind(this), false); - - xhr.addEventListener("error", (function() { - if (aCallback) - aCallback(false); - }).bind(this), false); - - xhr.send(null); - }, - - remoteMedia: function remoteMedia(aCallback, aListener) { - this.status((aStatus) => { - if (aStatus.serviceURL) { - if (aCallback) { - aCallback(new RemoteMedia(aStatus.serviceURL, aListener, this)); - } - return; - } - - // It can take a few moments for the player app to load. Let's use a small delay - // and retry a few times. - if (this.statusRetry < STATUS_RETRY_COUNT) { - this.statusRetry++; - this.statusTimer.initWithCallback(() => { - this.remoteMedia(aCallback, aListener); - }, STATUS_RETRY_WAIT, Ci.nsITimer.TYPE_ONE_SHOT); - } else { - // Fail - if (aCallback) { - aCallback(); - } - } - }); - } -} - -/* RemoteMedia provides a wrapper for using WebSockets and Flint protocol to control - * the Matchstick media player - * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs - * See: https://github.com/openflint/flint-receiver-sdk/blob/gh-pages/v1/libs/mediaplayer.js - */ -function RemoteMedia(aURL, aListener, aApp) { - this._active = false; - this._status = "uninitialized"; - - this.app = aApp; - this.listener = aListener; - - this.pingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - - let uri = Services.io.newURI(aURL, null, null); - this.ws = Cc["@mozilla.org/network/protocol;1?name=ws"].createInstance(Ci.nsIWebSocketChannel); - this.ws.initLoadInfo(null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_WEBSOCKET); - - this.ws.asyncOpen(uri, aURL, this, null); -} - -// Used to give us a small gap between not pinging too often and pinging too late -const PING_INTERVAL_BACKOFF = 200; - -RemoteMedia.prototype = { - _ping: function _ping() { - if (this.app.pingInterval == -1) { - return; - } - - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - xhr.open("GET", this.app.resourceURL, true); - xhr.setRequestHeader("Accept", "application/xml; charset=utf8"); - xhr.setRequestHeader("Authorization", this.app.token); - - xhr.addEventListener("load", () => { - if (xhr.status == 200) { - this.pingTimer.initWithCallback(() => { - this._ping(); - }, this.app.pingInterval - PING_INTERVAL_BACKOFF, Ci.nsITimer.TYPE_ONE_SHOT); - } - }); - - xhr.send(null); - }, - - _changeStatus: function _changeStatus(status) { - if (this._status != status) { - this._status = status; - if ("onRemoteMediaStatus" in this.listener) { - this.listener.onRemoteMediaStatus(this); - } - } - }, - - _teardown: function _teardown() { - if (!this._active) { - return; - } - - // Stop any queued ping event - this.pingTimer.cancel(); - - // Let the listener know we are finished - this._active = false; - if (this.listener && "onRemoteMediaStop" in this.listener) { - this.listener.onRemoteMediaStop(this); - } - }, - - _sendMsg: function _sendMsg(params) { - // Convert payload to a string - params.payload = JSON.stringify(params.payload); - - try { - this.ws.sendMsg(JSON.stringify(params)); - } catch (e if e.result == Cr.NS_ERROR_NOT_CONNECTED) { - // This shouldn't happen unless something gets out of sync with the - // connection. Let's make sure we try to cleanup. - this._teardown(); - } catch (e) { - log("Send Error: " + e) - } - }, - - get active() { - return this._active; - }, - - get status() { - return this._status; - }, - - shutdown: function shutdown() { - this.ws.close(Ci.nsIWebSocketChannel.CLOSE_NORMAL, "shutdown"); - }, - - play: function play() { - if (!this._active) { - return; - } - - let params = { - namespace: "urn:flint:org.openflint.fling.media", - payload: { - type: "PLAY", - requestId: "requestId-5", - } - }; - - this._sendMsg(params); - }, - - pause: function pause() { - if (!this._active) { - return; - } - - let params = { - namespace: "urn:flint:org.openflint.fling.media", - payload: { - type: "PAUSE", - requestId: "requestId-4", - } - }; - - this._sendMsg(params); - }, - - load: function load(aData) { - if (!this._active) { - return; - } - - let params = { - namespace: "urn:flint:org.openflint.fling.media", - payload: { - type: "LOAD", - requestId: "requestId-2", - media: { - contentId: aData.source, - contentType: "video/mp4", - metadata: { - title: "", - subtitle: "" - } - } - } - }; - - this._sendMsg(params); - }, - - onStart: function(aContext) { - this._active = true; - if (this.listener && "onRemoteMediaStart" in this.listener) { - this.listener.onRemoteMediaStart(this); - } - - this._ping(); - }, - - onStop: function(aContext, aStatusCode) { - // This will be called for internal socket failures and timeouts. Make - // sure we cleanup. - this._teardown(); - }, - - onAcknowledge: function(aContext, aSize) {}, - onBinaryMessageAvailable: function(aContext, aMessage) {}, - - onMessageAvailable: function(aContext, aMessage) { - let msg = JSON.parse(aMessage); - if (!msg) { - return; - } - - let payload = JSON.parse(msg.payload); - if (!payload) { - return; - } - - // Handle state changes using the player notifications - if (payload.type == "MEDIA_STATUS") { - let status = payload.status[0]; - let state = status.playerState.toLowerCase(); - if (state == "playing") { - this._changeStatus("started"); - } else if (state == "paused") { - this._changeStatus("paused"); - } else if (state == "idle" && "idleReason" in status) { - // Make sure we are really finished. IDLE can be sent at other times. - let reason = status.idleReason.toLowerCase(); - if (reason == "finished") { - this._changeStatus("completed"); - } - } - } - }, - - onServerClose: function(aContext, aStatusCode, aReason) { - // This will be fired from _teardown when we close the websocket, but it - // can also be called for other internal socket failures and timeouts. We - // make sure the _teardown bails on reentry. - this._teardown(); - } -} diff --git a/mobile/android/modules/moz.build b/mobile/android/modules/moz.build index a3ca1588f6f6..ed9ce49e93cc 100644 --- a/mobile/android/modules/moz.build +++ b/mobile/android/modules/moz.build @@ -17,7 +17,6 @@ EXTRA_JS_MODULES += [ 'JavaAddonManager.jsm', 'JNI.jsm', 'LightweightThemeConsumer.jsm', - 'MatchstickApp.jsm', 'MediaPlayerApp.jsm', 'Messaging.jsm', 'MulticastDNS.jsm', diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 07d5cb2b39f5..266f7290de4b 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -900,6 +900,9 @@ pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image"); // GCLI commands directory pref("devtools.commands.dir", ""); +// Allows setting the performance marks for which telemetry metrics will be recorded. +pref("devtools.telemetry.supported_performance_marks", "contentInteractive,navigationInteractive,navigationLoaded,visuallyLoaded,fullyLoaded,mediaEnumerated,scanEnd"); + // view source pref("view_source.syntax_highlight", true); pref("view_source.wrap_long_lines", false); diff --git a/testing/marionette/client/marionette/tests/webapi-tests.ini b/testing/marionette/client/marionette/tests/webapi-tests.ini index a0cfcb946109..7a7130e84d5e 100644 --- a/testing/marionette/client/marionette/tests/webapi-tests.ini +++ b/testing/marionette/client/marionette/tests/webapi-tests.ini @@ -22,7 +22,9 @@ skip = false [include:../../../../../dom/icc/tests/marionette/manifest.ini] [include:../../../../../dom/system/tests/marionette/manifest.ini] [include:../../../../../dom/nfc/tests/marionette/manifest.ini] +skip-if = android_version > '15' # Bug 1203072 [include:../../../../../dom/events/test/marionette/manifest.ini] [include:../../../../../dom/wifi/test/marionette/manifest.ini] [include:../../../../../dom/cellbroadcast/tests/marionette/manifest.ini] [include:../../../../../dom/tethering/tests/marionette/manifest.ini] +skip-if = android_version > '15' # Bug 1203075 diff --git a/testing/mochitest/mochitest_options.py b/testing/mochitest/mochitest_options.py index 64f3959fb927..8a0ccf3c588f 100644 --- a/testing/mochitest/mochitest_options.py +++ b/testing/mochitest/mochitest_options.py @@ -78,7 +78,6 @@ class MochitestArguments(ArgumentContainer): "help": "Override the default binary used to run tests with the path provided, e.g " "/usr/bin/firefox. If you have run ./mach package beforehand, you can " "specify 'dist' to run tests against the distribution bundle's binary.", - "suppress": build_obj is not None, }], [["--utility-path"], {"dest": "utilityPath", diff --git a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul index 4810443450d3..d9f1b81064e3 100644 --- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul +++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul @@ -39,6 +39,11 @@ const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE; const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE; + // Use backslashes instead of forward slashes due to memory reporting's hacky + // handling of URLs. + const XUL_NS = + "http:\\\\www.mozilla.org\\keymaster\\gatekeeper\\there.is.only.xul"; + let vsizeAmounts = []; let residentAmounts = []; let heapAllocatedAmounts = []; @@ -119,13 +124,16 @@ function handleReportAnonymized(aProcess, aPath, aKind, aUnits, aAmount, aDescription) { + // Path might include an xmlns using http, which is safe to ignore. + let reducedPath = aPath.replace(XUL_NS, ""); + // Shouldn't get http: or https: in any paths. - if (aPath.includes('http:')) { + if (reducedPath.includes('http:')) { present.httpWhenAnonymized = aPath; } // file: URLs should have their path anonymized. - if (aPath.search('file:..[^<]') !== -1) { + if (reducedPath.search('file:..[^<]') !== -1) { present.unanonymizedFilePathWhenAnonymized = aPath; } } @@ -391,4 +399,3 @@ ]]> - diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index aab5b8b22685..0a4492385009 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -8890,6 +8890,125 @@ "keyed": "true", "description": "Number of warnings, keyed by appName." }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_CONTENTINTERACTIVE": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'contentInteractive' performance mark, keyed by appName.", + "high": "2000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_NAVIGATIONINTERACTIVE": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'navigationInteractive' performance mark, keyed by appName.", + "high": "3000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_NAVIGATIONLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'navigationLoaded' performance mark, keyed by appName.", + "high": "4000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_VISUALLYLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'visuallyLoaded' performance mark, keyed by appName.", + "high": "5000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_MEDIAENUMERATED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'mediaEnumerated' performance mark, keyed by appName.", + "high": "5000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_FULLYLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'fullyLoaded' performance mark, keyed by appName.", + "high": "30000", + "n_buckets": 30 + }, + "DEVTOOLS_HUD_APP_STARTUP_TIME_SCANEND": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The duration in ms between application launch and the 'scanEnd' performance mark, keyed by appName.", + "high": "30000", + "n_buckets": 30 + }, + "DEVTOOLS_HUD_APP_MEMORY_CONTENTINTERACTIVE": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'contentInteractive' performance mark, keyed by appName.", + "low": "2000000", + "high": "3000000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_MEMORY_NAVIGATIONINTERACTIVE": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'navigationInteractive' performance mark, keyed by appName.", + "low": "2000000", + "high": "3000000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_MEMORY_NAVIGATIONLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'navigationLoaded' performance mark, keyed by appName.", + "low": "2000000", + "high": "3000000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_MEMORY_VISUALLYLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'visuallyLoaded' performance mark, keyed by appName.", + "low": "2000000", + "high": "3000000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_MEMORY_MEDIAENUMERATED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'mediaEnumerated' performance mark, keyed by appName.", + "low": "2000000", + "high": "3000000", + "n_buckets": 10 + }, + "DEVTOOLS_HUD_APP_MEMORY_FULLYLOADED": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'fullyLoaded' performance mark, keyed by appName.", + "low": "2000000", + "high": "4000000", + "n_buckets": 20 + }, + "DEVTOOLS_HUD_APP_MEMORY_SCANEND": { + "expires_in_version": "never", + "kind": "linear", + "keyed": "true", + "description": "The USS memory consumed by an application at the time of the 'scanEnd' performance mark, keyed by appName.", + "low": "2000000", + "high": "4000000", + "n_buckets": 20 + }, "GRAPHICS_SANITY_TEST_REASON": { "alert_emails": ["danderson@mozilla.com"], "expires_in_version": "43", diff --git a/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp b/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp index 04c28a1c7ee1..d6c5ce3d0dbf 100644 --- a/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp +++ b/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp @@ -28,10 +28,12 @@ #include "jsapi.h" #include "nsCycleCollectionParticipant.h" #include "nsCRTGlue.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" #include "nsIOutputStream.h" #include "nsISupportsImpl.h" #include "nsNetUtil.h" -#include "nsIFile.h" +#include "nsPrintfCString.h" #include "prerror.h" #include "prio.h" #include "prtypes.h" @@ -815,11 +817,45 @@ namespace dom { using namespace JS; using namespace devtools; +static unsigned long +msSinceProcessCreation(const TimeStamp& now) +{ + bool ignored; + auto duration = now - TimeStamp::ProcessCreation(ignored); + return (unsigned long) duration.ToMilliseconds(); +} + +// Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap +// snapshots are serialized into. +static already_AddRefed +createUniqueCoreDumpFile(ErrorResult& rv, const TimeStamp& now, nsAString& outFilePath) +{ + nsCOMPtr file; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)); + if (NS_WARN_IF(rv.Failed())) + return nullptr; + + auto ms = msSinceProcessCreation(now); + rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms)); + if (NS_WARN_IF(rv.Failed())) + return nullptr; + + rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); + if (NS_WARN_IF(rv.Failed())) + return nullptr; + + rv = file->GetPath(outFilePath); + if (NS_WARN_IF(rv.Failed())) + return nullptr; + + return file.forget(); +} + /* static */ void ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global, JSContext* cx, - const nsAString& filePath, const HeapSnapshotBoundaries& boundaries, + nsAString& outFilePath, ErrorResult& rv) { auto start = TimeStamp::Now(); @@ -829,8 +865,7 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global, uint32_t nodeCount = 0; uint32_t edgeCount = 0; - nsCOMPtr file; - rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file)); + nsCOMPtr file = createUniqueCoreDumpFile(rv, start, outFilePath); if (NS_WARN_IF(rv.Failed())) return; diff --git a/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html b/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html index 2b94851faecd..f150a99c7ab9 100644 --- a/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html +++ b/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html @@ -15,13 +15,7 @@ Bug 1024774 - Sanity test that we can take a heap snapshot in a web window. SimpleTest.waitForExplicitFinish(); window.onload = function() { ok(ChromeUtils, "The ChromeUtils interface should be exposed in chrome windows."); - - var file = Components.classes["@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties) - .get("CurWorkD", Components.interfaces.nsILocalFile); - file.append("core-dump.tmp"); - - ChromeUtils.saveHeapSnapshot(file.path, { runtime: true }); + ChromeUtils.saveHeapSnapshot({ runtime: true }); ok(true, "Should save a heap snapshot and shouldn't throw."); SimpleTest.finish(); }; diff --git a/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js b/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js index 77bc4e42f830..ee08ca4f9fcf 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js @@ -106,13 +106,10 @@ function getFilePath(aName, aAllowMissing=false, aUsePlatformPathSeparator=false return path; } -function saveNewHeapSnapshot(fileName=`core-dump-${Math.random()}.tmp`) { - const filePath = getFilePath(fileName, true, true); +function saveNewHeapSnapshot() { + const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true }); ok(filePath, "Should get a file path to save the core dump to."); - - ChromeUtils.saveHeapSnapshot(filePath, { runtime: true }); ok(true, "Saved a heap snapshot to " + filePath); - return filePath; } @@ -135,16 +132,10 @@ function saveNewHeapSnapshot(fileName=`core-dump-${Math.random()}.tmp`) { * * @returns Census */ -function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined, - // Add the Math.random() so that parallel - // tests are less likely to mess with - // each other. - fileName="core-dump-" + (Math.random()) + ".tmp") { - const filePath = getFilePath(fileName, true, true); - ok(filePath, "Should get a file path to save the core dump to."); - +function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) { const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true }; - ChromeUtils.saveHeapSnapshot(filePath, snapshotOptions); + const filePath = ChromeUtils.saveHeapSnapshot(snapshotOptions); + ok(filePath, "Should get a file path to save the core dump to."); ok(true, "Should have saved a heap snapshot to " + filePath); const snapshot = ChromeUtils.readHeapSnapshot(filePath); diff --git a/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js b/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js index 0219cba478b3..10ee70cec62b 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js @@ -6,8 +6,6 @@ console.log("Initializing worker."); self.onmessage = e => { console.log("Starting test."); try { - const { filePath } = e.data; - ok(typeof ChromeUtils === "undefined", "Should not have access to ChromeUtils in a worker."); ok(ThreadSafeChromeUtils, @@ -15,7 +13,7 @@ self.onmessage = e => { ok(HeapSnapshot, "Should have access to HeapSnapshot in a worker."); - ThreadSafeChromeUtils.saveHeapSnapshot(filePath, { globals: [this] }); + const filePath = ThreadSafeChromeUtils.saveHeapSnapshot({ globals: [this] }); ok(true, "Should be able to save a snapshot."); const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath); diff --git a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js index 5fd38ce9213f..dde139ffd2db 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js @@ -9,10 +9,7 @@ if (typeof Debugger != "function") { } function run_test() { - const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true); - ok(filePath, "Should get a file path"); - - ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] }); + const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] }); ok(true, "Should be able to save a snapshot."); const snapshot = ChromeUtils.readHeapSnapshot(filePath); diff --git a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js index e5d1f5f71e60..d91f36f5654b 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js @@ -25,10 +25,7 @@ function run_test() { // Now save a snapshot that will include the allocation stacks and read it // back again. - const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true); - ok(filePath, "Should get a file path"); - - ChromeUtils.saveHeapSnapshot(filePath, { runtime: true }); + const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true }); ok(true, "Should be able to save a snapshot."); const snapshot = ChromeUtils.readHeapSnapshot(filePath); diff --git a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js index 9567bfeae9f2..76461b694aea 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js @@ -4,11 +4,8 @@ // Test that we can read core dumps into HeapSnapshot instances in a worker. add_task(function* () { - const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true); - ok(filePath, "Should get a file path"); - const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js"); - worker.postMessage({ filePath }); + worker.postMessage({}); let assertionCount = 0; worker.onmessage = e => { diff --git a/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js b/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js index 3d46e3b1056d..f24b028238fc 100644 --- a/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js +++ b/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js @@ -11,89 +11,72 @@ if (typeof Debugger != "function") { function run_test() { ok(ChromeUtils, "Should be able to get the ChromeUtils interface"); - let filePath = getFilePath("core-dump.tmp", true, true); - ok(filePath, "Should get a file path"); - - testBadParameters(filePath); - testGoodParameters(filePath); + testBadParameters(); + testGoodParameters(); do_test_finished(); } -function testBadParameters(filePath) { +function testBadParameters() { throws(() => ChromeUtils.saveHeapSnapshot(), "Should throw if arguments aren't passed in."); - throws(() => ChromeUtils.saveHeapSnapshot(Object.create(null), - { runtime: true }), - "Should throw if the filePath is not coercible to string."); - - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - null), + throws(() => ChromeUtils.saveHeapSnapshot(null), "Should throw if boundaries isn't an object."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - {}), + throws(() => ChromeUtils.saveHeapSnapshot({}), "Should throw if the boundaries object doesn't have any properties."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { runtime: true, + throws(() => ChromeUtils.saveHeapSnapshot({ runtime: true, globals: [this] }), "Should throw if the boundaries object has more than one property."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { debugger: {} }), + throws(() => ChromeUtils.saveHeapSnapshot({ debugger: {} }), "Should throw if the debuggees object is not a Debugger object"); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { globals: [{}] }), + throws(() => ChromeUtils.saveHeapSnapshot({ globals: [{}] }), "Should throw if the globals array contains non-global objects."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { runtime: false }), + throws(() => ChromeUtils.saveHeapSnapshot({ runtime: false }), "Should throw if runtime is supplied and is not true."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { globals: null }), + throws(() => ChromeUtils.saveHeapSnapshot({ globals: null }), "Should throw if globals is not an object."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { globals: {} }), + throws(() => ChromeUtils.saveHeapSnapshot({ globals: {} }), "Should throw if globals is not an array."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { debugger: Debugger.prototype }), + throws(() => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }), "Should throw if debugger is the Debugger.prototype object."); - throws(() => ChromeUtils.saveHeapSnapshot(filePath, - { get globals() { return [this]; } }), + throws(() => ChromeUtils.saveHeapSnapshot({ get globals() { return [this]; } }), "Should throw if boundaries property is a getter."); } const makeNewSandbox = () => Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')()); -function testGoodParameters(filePath) { +function testGoodParameters() { let sandbox = makeNewSandbox(); let dbg = new Debugger(sandbox); - ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg }); + ChromeUtils.saveHeapSnapshot({ debugger: dbg }); ok(true, "Should be able to save a snapshot for a debuggee global."); dbg = new Debugger; let sandboxes = Array(10).fill(null).map(makeNewSandbox); sandboxes.forEach(sb => dbg.addDebuggee(sb)); - ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg }); + ChromeUtils.saveHeapSnapshot({ debugger: dbg }); ok(true, "Should be able to save a snapshot for many debuggee globals."); dbg = new Debugger; - ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg }); + ChromeUtils.saveHeapSnapshot({ debugger: dbg }); ok(true, "Should be able to save a snapshot with no debuggee globals."); - ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] }); + ChromeUtils.saveHeapSnapshot({ globals: [this] }); ok(true, "Should be able to save a snapshot for a specific global."); - ChromeUtils.saveHeapSnapshot(filePath, { runtime: true }); + ChromeUtils.saveHeapSnapshot({ runtime: true }); ok(true, "Should be able to save a snapshot of the full runtime."); } diff --git a/toolkit/devtools/server/actors/call-watcher.js b/toolkit/devtools/server/actors/call-watcher.js index eeb12a080441..5a49b6cc70f0 100644 --- a/toolkit/devtools/server/actors/call-watcher.js +++ b/toolkit/devtools/server/actors/call-watcher.js @@ -55,6 +55,8 @@ let FunctionCallActor = protocol.ActorClass({ * The called function's name. * @param array stack * The called function's stack, as a list of { name, file, line } objects. + * @param number timestamp + * The timestamp of draw-related functions * @param array args * The called function's arguments. * @param any result @@ -63,13 +65,14 @@ let FunctionCallActor = protocol.ActorClass({ * Determines whether or not FunctionCallActor stores a weak reference * to the underlying objects. */ - initialize: function(conn, [window, global, caller, type, name, stack, args, result], holdWeak) { + initialize: function(conn, [window, global, caller, type, name, stack, timestamp, args, result], holdWeak) { protocol.Actor.prototype.initialize.call(this, conn); this.details = { type: type, name: name, stack: stack, + timestamp: timestamp }; // Store a weak reference to all objects so we don't @@ -87,7 +90,8 @@ let FunctionCallActor = protocol.ActorClass({ window: { get: () => weakRefs.window.get() }, caller: { get: () => weakRefs.caller.get() }, result: { get: () => weakRefs.result.get() }, - args: { get: () => weakRefs.args.get() } + args: { get: () => weakRefs.args.get() }, + timestamp: { get: () => weakRefs.timestamp.get() }, }); } // Otherwise, hold strong references to the objects. @@ -96,6 +100,7 @@ let FunctionCallActor = protocol.ActorClass({ this.details.caller = caller; this.details.result = result; this.details.args = args; + this.details.timestamp = timestamp; } this.meta = { @@ -128,6 +133,7 @@ let FunctionCallActor = protocol.ActorClass({ name: this.details.name, file: this.details.stack[0].file, line: this.details.stack[0].line, + timestamp: this.details.timestamp, callerPreview: this.meta.previews.caller, argsPreview: this.meta.previews.args }; @@ -138,7 +144,7 @@ let FunctionCallActor = protocol.ActorClass({ * available on the Front instance. */ getDetails: method(function() { - let { type, name, stack } = this.details; + let { type, name, stack, timestamp } = this.details; // Since not all calls on the stack have corresponding owner files (e.g. // callbacks of a requestAnimationFrame etc.), there's no benefit in @@ -156,7 +162,8 @@ let FunctionCallActor = protocol.ActorClass({ return { type: type, name: name, - stack: stack + stack: stack, + timestamp: timestamp }; }, { response: { info: RetVal("call-details") } @@ -243,6 +250,7 @@ let FunctionCallFront = protocol.FrontClass(FunctionCallActor, { this.name = form.name; this.file = form.file; this.line = form.line; + this.timestamp = form.timestamp; this.callerPreview = form.callerPreview; this.argsPreview = form.argsPreview; } @@ -333,6 +341,13 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ response: RetVal("boolean") }), + /** + * Initialize frame start timestamp for measuring + */ + initFrameStartTimestamp: method(function() { + this._frameStartTimestamp = this.tabActor.window.performance.now(); + }), + /** * Starts recording function calls. */ @@ -424,9 +439,10 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ } if (self._recording) { + let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp; let stack = getStack(name); let type = CallWatcherFront.METHOD_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, result); + callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result); } return result; }, target, { defineAs: name }); @@ -453,9 +469,10 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ let result = Cu.waiveXrays(originalGetter.apply(this, args)); if (self._recording) { + let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp; let stack = getStack(name); let type = CallWatcherFront.GETTER_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, result); + callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result); } return result; }, @@ -464,9 +481,10 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ originalSetter.apply(this, args); if (self._recording) { + let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp; let stack = getStack(name); let type = CallWatcherFront.SETTER_FUNCTION; - callback(unwrappedWindow, global, this, type, name, stack, args, undefined); + callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, undefined); } }, configurable: descriptor.configurable, diff --git a/toolkit/devtools/server/actors/canvas.js b/toolkit/devtools/server/actors/canvas.js index 9f7ce2707741..33843c9512e0 100644 --- a/toolkit/devtools/server/actors/canvas.js +++ b/toolkit/devtools/server/actors/canvas.js @@ -316,6 +316,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({ this._recordingContainsDrawCall = false; this._callWatcher.eraseRecording(); + this._callWatcher.initFrameStartTimestamp(); this._callWatcher.resumeRecording(); let deferred = this._currentAnimationFrameSnapshot = promise.defer(); diff --git a/toolkit/devtools/shared/memory.js b/toolkit/devtools/shared/memory.js index 2550191576c9..cc41a179be3d 100644 --- a/toolkit/devtools/shared/memory.js +++ b/toolkit/devtools/shared/memory.js @@ -139,8 +139,7 @@ let Memory = exports.Memory = Class({ * @returns {String} The snapshot id. */ saveHeapSnapshot: expectState("attached", function () { - const path = HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath(); - ThreadSafeChromeUtils.saveHeapSnapshot(path, { debugger: this.dbg }); + const path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg }); return HeapSnapshotFileUtils.getSnapshotIdFromPath(path); }, "saveHeapSnapshot"), diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 50734ac87d09..213946b73620 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -2118,8 +2118,10 @@ this.XPIDatabaseReconcile = { } } - // None of the active add-ons match the selected theme, enable the default. - if (!sawActiveTheme) { + // If a custom theme is selected and it wasn't seen in the new list of + // active add-ons then enable the default theme + if (XPIProvider.selectedSkin != XPIProvider.defaultSkin && !sawActiveTheme) { + logger.info("Didn't see selected skin " + XPIProvider.selectedSkin); XPIProvider.enableDefaultTheme(); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js index cd49de25e067..890a3b481d86 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js @@ -1089,7 +1089,51 @@ function run_test_21() { p1.userDisabled = false; ensure_test_completed(); - end_test(); + run_test_22(); }); })); } + +// Detecting a new add-on during the startup file check should not disable an +// active lightweight theme +function run_test_22() { + restartManager(); + + AddonManager.getAddonsByIDs(["default@tests.mozilla.org", + "1@personas.mozilla.org"], function([d, p1]) { + do_check_true(d.userDisabled); + do_check_false(d.appDisabled); + do_check_false(d.isActive); + + do_check_false(p1.userDisabled); + do_check_false(p1.appDisabled); + do_check_true(p1.isActive); + + writeInstallRDFForExtension({ + id: "theme3@tests.mozilla.org", + version: "1.0", + name: "Test 3", + internalName: "theme3/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }] + }, profileDir); + + restartManager(); + + AddonManager.getAddonsByIDs(["default@tests.mozilla.org", + "1@personas.mozilla.org"], function([d, p1]) { + do_check_true(d.userDisabled); + do_check_false(d.appDisabled); + do_check_false(d.isActive); + + do_check_false(p1.userDisabled); + do_check_false(p1.appDisabled); + do_check_true(p1.isActive); + + end_test(); + }); + }); +}