diff --git a/accessible/src/base/nsEventShell.cpp b/accessible/src/base/nsEventShell.cpp index 06a7084b3f72..8d0a5094d2e1 100644 --- a/accessible/src/base/nsEventShell.cpp +++ b/accessible/src/base/nsEventShell.cpp @@ -7,6 +7,9 @@ #include "nsAccUtils.h" +#include "mozilla/StaticPtr.h" + +using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// @@ -61,4 +64,4 @@ nsEventShell::GetEventAttributes(nsINode *aNode, // nsEventShell: private bool nsEventShell::sEventFromUserInput = false; -nsCOMPtr nsEventShell::sEventTargetNode; +StaticRefPtr nsEventShell::sEventTargetNode; diff --git a/accessible/src/base/nsEventShell.h b/accessible/src/base/nsEventShell.h index 2934ca8be82d..9ccc0df54545 100644 --- a/accessible/src/base/nsEventShell.h +++ b/accessible/src/base/nsEventShell.h @@ -8,6 +8,9 @@ #include "AccEvent.h" +namespace mozilla { +template class StaticRefPtr; +} class nsIPersistentProperties; /** @@ -43,7 +46,7 @@ public: nsIPersistentProperties *aAttributes); private: - static nsCOMPtr sEventTargetNode; + static mozilla::StaticRefPtr sEventTargetNode; static bool sEventFromUserInput; }; diff --git a/accessible/src/base/nsTextEquivUtils.cpp b/accessible/src/base/nsTextEquivUtils.cpp index 959ba54738d4..9f0e366ac6f8 100644 --- a/accessible/src/base/nsTextEquivUtils.cpp +++ b/accessible/src/base/nsTextEquivUtils.cpp @@ -19,6 +19,13 @@ using namespace mozilla::a11y; +/** + * The accessible for which we are computing a text equivalent. It is useful + * for bailing out during recursive text computation, or for special cases + * like step f. of the ARIA implementation guide. + */ +static Accessible* sInitiatorAcc = nullptr; + //////////////////////////////////////////////////////////////////////////////// // nsTextEquivUtils. Public. @@ -28,10 +35,10 @@ nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible, { aName.Truncate(); - if (gInitiatorAcc) + if (sInitiatorAcc) return NS_OK; - gInitiatorAcc = aAccessible; + sInitiatorAcc = aAccessible; if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) { //XXX: is it necessary to care the accessible is not a document? if (aAccessible->IsContent()) { @@ -43,7 +50,7 @@ nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible, } } - gInitiatorAcc = nullptr; + sInitiatorAcc = nullptr; return NS_OK; } @@ -92,10 +99,10 @@ nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc, nsAString *aString) { // Prevent recursion which can cause infinite loops. - if (gInitiatorAcc) + if (sInitiatorAcc) return NS_OK; - gInitiatorAcc = aInitiatorAcc; + sInitiatorAcc = aInitiatorAcc; // If the given content is not visible or isn't accessible then go down // through the DOM subtree otherwise go down through accessible subtree and @@ -108,7 +115,7 @@ nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc, if (isVisible) { Accessible* accessible = - gInitiatorAcc->Document()->GetAccessible(aContent); + sInitiatorAcc->Document()->GetAccessible(aContent); if (accessible) { rv = AppendFromAccessible(accessible, aString); goThroughDOMSubtree = false; @@ -118,7 +125,7 @@ nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc, if (goThroughDOMSubtree) rv = AppendFromDOMNode(aContent, aString); - gInitiatorAcc = nullptr; + sInitiatorAcc = nullptr; return rv; } @@ -176,8 +183,6 @@ nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent, //////////////////////////////////////////////////////////////////////////////// // nsTextEquivUtils. Private. -nsRefPtr nsTextEquivUtils::gInitiatorAcc; - nsresult nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible, nsAString *aString) @@ -257,7 +262,7 @@ nsTextEquivUtils::AppendFromValue(Accessible* aAccessible, // value if and only if the given accessible is in the middle of its parent. nsAutoString text; - if (aAccessible != gInitiatorAcc) { + if (aAccessible != sInitiatorAcc) { aAccessible->Value(text); return AppendString(aString, text) ? diff --git a/accessible/src/base/nsTextEquivUtils.h b/accessible/src/base/nsTextEquivUtils.h index a8faa4c49be9..30c87c25b2a7 100644 --- a/accessible/src/base/nsTextEquivUtils.h +++ b/accessible/src/base/nsTextEquivUtils.h @@ -153,13 +153,6 @@ private: * Returns the rule (constant of ETextEquivRule) for a given role. */ static uint32_t GetRoleRule(mozilla::a11y::roles::Role aRole); - - /** - * The accessible for which we are computing a text equivalent. It is useful - * for bailing out during recursive text computation, or for special cases - * like step f. of the ARIA implementation guide. - */ - static nsRefPtr gInitiatorAcc; }; #endif diff --git a/accessible/src/jsat/TraversalRules.jsm b/accessible/src/jsat/TraversalRules.jsm index f9ce59d1bb3e..6cb47ec5ce21 100644 --- a/accessible/src/jsat/TraversalRules.jsm +++ b/accessible/src/jsat/TraversalRules.jsm @@ -26,7 +26,8 @@ BaseTraversalRule.prototype = { }, preFilter: Ci.nsIAccessibleTraversalRule.PREFILTER_DEFUNCT | - Ci.nsIAccessibleTraversalRule.PREFILTER_INVISIBLE, + Ci.nsIAccessibleTraversalRule.PREFILTER_INVISIBLE | + Ci.nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN, match: function BaseTraversalRule_match(aAccessible) { diff --git a/accessible/src/jsat/Utils.jsm b/accessible/src/jsat/Utils.jsm index 47b9d450ca15..17c545f48f9f 100644 --- a/accessible/src/jsat/Utils.jsm +++ b/accessible/src/jsat/Utils.jsm @@ -184,6 +184,21 @@ this.Utils = { return [state.value, extState.value]; }, + getAttributes: function getAttributes(aAccessible) { + let attributesEnum = aAccessible.attributes.enumerate(); + let attributes = {}; + + // Populate |attributes| object with |aAccessible|'s attribute key-value + // pairs. + while (attributesEnum.hasMoreElements()) { + let attribute = attributesEnum.getNext().QueryInterface( + Ci.nsIPropertyElement); + attributes[attribute.key] = attribute.value; + } + + return attributes; + }, + getVirtualCursor: function getVirtualCursor(aDocument) { let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument : this.AccRetrieval.getAccessibleFor(aDocument); diff --git a/accessible/src/jsat/UtteranceGenerator.jsm b/accessible/src/jsat/UtteranceGenerator.jsm index bbb9e03f5f30..fa803a6460fd 100644 --- a/accessible/src/jsat/UtteranceGenerator.jsm +++ b/accessible/src/jsat/UtteranceGenerator.jsm @@ -12,6 +12,7 @@ const Cr = Components.results; const INCLUDE_DESC = 0x01; const INCLUDE_NAME = 0x02; const INCLUDE_CUSTOM = 0x04; +const NAME_FROM_SUBTREE_RULE = 0x08; const UTTERANCE_DESC_FIRST = 0; @@ -74,19 +75,32 @@ this.UtteranceGenerator = { utterance.push.apply(utterance, UtteranceGenerator.genForObject(aAccessible)); }; - + let roleString = Utils.AccRetrieval.getStringRole(aContext.accessible.role); + let nameRule = this.roleRuleMap[roleString] || 0; let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST; + // Include subtree if the name is not explicit or the role's name rule is + // not the NAME_FROM_SUBTREE_RULE. + let includeSubtree = (Utils.getAttributes(aContext.accessible)[ + 'explicit-name'] !== 'true') || !(nameRule & NAME_FROM_SUBTREE_RULE); if (utteranceOrder === UTTERANCE_DESC_FIRST) { aContext.newAncestry.forEach(addUtterance); addUtterance(aContext.accessible); - aContext.subtreePreorder.forEach(addUtterance); + if (includeSubtree) { + aContext.subtreePreorder.forEach(addUtterance); + } } else { - aContext.subtreePostorder.forEach(addUtterance); + if (includeSubtree) { + aContext.subtreePostorder.forEach(addUtterance); + } addUtterance(aContext.accessible); aContext.newAncestry.reverse().forEach(addUtterance); } + // Clean up the white space. + let trimmed; + utterance = [trimmed for (word of utterance) if (trimmed = word.trim())]; + return utterance; }, @@ -98,7 +112,7 @@ this.UtteranceGenerator = { * @return {Array} Two string array. The first string describes the object * and its states. The second string is the object's name. Whether the * object's description or it's role is included is determined by - * {@link verbosityRoleMap}. + * {@link roleRuleMap}. */ genForObject: function genForObject(aAccessible) { let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role); @@ -106,7 +120,7 @@ this.UtteranceGenerator = { let func = this.objectUtteranceFunctions[roleString] || this.objectUtteranceFunctions.defaultFunc; - let flags = this.verbosityRoleMap[roleString] || UTTERANCE_DESC_FIRST; + let flags = this.roleRuleMap[roleString] || 0; if (aAccessible.childCount == 0) flags |= INCLUDE_NAME; @@ -182,29 +196,35 @@ this.UtteranceGenerator = { aIsEditing ? 'editingMode' : 'navigationMode')]; }, - verbosityRoleMap: { + roleRuleMap: { 'menubar': INCLUDE_DESC, 'scrollbar': INCLUDE_DESC, 'grip': INCLUDE_DESC, 'alert': INCLUDE_DESC | INCLUDE_NAME, 'menupopup': INCLUDE_DESC, - 'menuitem': INCLUDE_DESC, - 'tooltip': INCLUDE_DESC, + 'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'columnheader': NAME_FROM_SUBTREE_RULE, + 'rowheader': NAME_FROM_SUBTREE_RULE, + 'column': NAME_FROM_SUBTREE_RULE, + 'row': NAME_FROM_SUBTREE_RULE, 'application': INCLUDE_NAME, 'document': INCLUDE_NAME, 'grouping': INCLUDE_DESC | INCLUDE_NAME, 'toolbar': INCLUDE_DESC, 'table': INCLUDE_DESC | INCLUDE_NAME, - 'link': INCLUDE_DESC, + 'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'helpballoon': NAME_FROM_SUBTREE_RULE, 'list': INCLUDE_DESC | INCLUDE_NAME, - 'listitem': INCLUDE_DESC, + 'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 'outline': INCLUDE_DESC, - 'outlineitem': INCLUDE_DESC, - 'pagetab': INCLUDE_DESC, + 'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 'graphic': INCLUDE_DESC, - 'pushbutton': INCLUDE_DESC, - 'checkbutton': INCLUDE_DESC, - 'radiobutton': INCLUDE_DESC, + 'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'buttondropdown': NAME_FROM_SUBTREE_RULE, 'combobox': INCLUDE_DESC, 'droplist': INCLUDE_DESC, 'progressbar': INCLUDE_DESC, @@ -213,15 +233,20 @@ this.UtteranceGenerator = { 'diagram': INCLUDE_DESC, 'animation': INCLUDE_DESC, 'equation': INCLUDE_DESC, - 'buttonmenu': INCLUDE_DESC, + 'buttonmenu': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'buttondropdowngrid': NAME_FROM_SUBTREE_RULE, 'pagetablist': INCLUDE_DESC, 'canvas': INCLUDE_DESC, - 'check menu item': INCLUDE_DESC, - 'label': INCLUDE_DESC, + 'check menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'label': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 'password text': INCLUDE_DESC, 'popup menu': INCLUDE_DESC, - 'radio menu item': INCLUDE_DESC, - 'toggle button': INCLUDE_DESC, + 'radio menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'table column header': NAME_FROM_SUBTREE_RULE, + 'table row header': NAME_FROM_SUBTREE_RULE, + 'tear off menu item': NAME_FROM_SUBTREE_RULE, + 'toggle button': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'parent menuitem': NAME_FROM_SUBTREE_RULE, 'header': INCLUDE_DESC, 'footer': INCLUDE_DESC, 'entry': INCLUDE_DESC | INCLUDE_NAME, @@ -230,7 +255,14 @@ this.UtteranceGenerator = { 'heading': INCLUDE_DESC, 'calendar': INCLUDE_DESC | INCLUDE_NAME, 'combobox list': INCLUDE_DESC, - 'combobox option': INCLUDE_DESC, + 'combobox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, + 'listbox option': NAME_FROM_SUBTREE_RULE, + 'listbox rich option': NAME_FROM_SUBTREE_RULE, + 'gridcell': NAME_FROM_SUBTREE_RULE, + 'check rich option': NAME_FROM_SUBTREE_RULE, + 'term': NAME_FROM_SUBTREE_RULE, + 'definition': NAME_FROM_SUBTREE_RULE, + 'key': NAME_FROM_SUBTREE_RULE, 'image map': INCLUDE_DESC, 'option': INCLUDE_DESC, 'listbox': INCLUDE_DESC, @@ -314,12 +346,16 @@ this.UtteranceGenerator = { }, _addName: function _addName(utterance, aAccessible, aFlags) { - let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : ''; - let utteranceOrder = gUtteranceOrder.value || 0; + let name; + if (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' || + (aFlags & INCLUDE_NAME)) { + name = aAccessible.name; + } if (name) { + let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST; utterance[utteranceOrder === UTTERANCE_DESC_FIRST ? - "push" : "unshift"](name); + 'push' : 'unshift'](name); } }, diff --git a/accessible/tests/mochitest/events/test_focus_general.xul b/accessible/tests/mochitest/events/test_focus_general.xul index 7a647c15b5dc..b5c18e8c5954 100644 --- a/accessible/tests/mochitest/events/test_focus_general.xul +++ b/accessible/tests/mochitest/events/test_focus_general.xul @@ -21,9 +21,6 @@ src="../events.js" /> + + + + + +
+ + Mozilla Bug 845870 + +

+ +

+    
+    
+    
+    This is a link
+    
+    
+    

This is the heading.

+

+ This is the heading. +

+
    +
  1. list one
  2. +
  3. list two
  4. +
+
+
dd one
+
+ + + + + +
Fruits and vegetables
+ +
+ + +
+ + \ No newline at end of file diff --git a/accessible/tests/mochitest/jsat/test_utterance_order.html b/accessible/tests/mochitest/jsat/test_utterance_order.html index 8819cf7e28f1..8cc34274c3f1 100644 --- a/accessible/tests/mochitest/jsat/test_utterance_order.html +++ b/accessible/tests/mochitest/jsat/test_utterance_order.html @@ -12,111 +12,79 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + + + \ No newline at end of file diff --git a/browser/devtools/shared/DeveloperToolbar.jsm b/browser/devtools/shared/DeveloperToolbar.jsm index 3fc3f778e6b8..f5b19139c947 100644 --- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -25,7 +25,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "gcli", XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands", "resource:///modules/devtools/BuiltinCommands.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener", +XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener", "resource://gre/modules/devtools/WebConsoleUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", @@ -444,8 +444,8 @@ DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab) } let window = aTab.linkedBrowser.contentWindow; - let listener = new PageErrorListener(window, { - onPageError: this._onPageError.bind(this, tabId), + let listener = new ConsoleServiceListener(window, { + onConsoleServiceMessage: this._onPageError.bind(this, tabId), }); listener.init(); diff --git a/browser/devtools/webconsole/test/Makefile.in b/browser/devtools/webconsole/test/Makefile.in index 7e03898544c5..84cb0cc3303a 100644 --- a/browser/devtools/webconsole/test/Makefile.in +++ b/browser/devtools/webconsole/test/Makefile.in @@ -131,6 +131,7 @@ MOCHITEST_BROWSER_FILES = \ browser_console_native_getters.js \ browser_bug_871156_ctrlw_close_tab.js \ browser_console_private_browsing.js \ + browser_console_nsiconsolemessage.js \ head.js \ $(NULL) diff --git a/browser/devtools/webconsole/test/browser_console.js b/browser/devtools/webconsole/test/browser_console.js index 2fa99a5b3a4c..d204c1b2bbb9 100644 --- a/browser/devtools/webconsole/test/browser_console.js +++ b/browser/devtools/webconsole/test/browser_console.js @@ -9,6 +9,15 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te function test() { + let oldFunction = HUDConsoleUI.toggleBrowserConsole; + let functionExecuted = false; + HUDConsoleUI.toggleBrowserConsole = () => functionExecuted = true; + EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, content); + + ok(functionExecuted, + "toggleBrowserConsole() was executed by the Ctrl-Shift-J key shortcut"); + + HUDConsoleUI.toggleBrowserConsole = oldFunction; HUDConsoleUI.toggleBrowserConsole().then(consoleOpened); } diff --git a/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js b/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js new file mode 100644 index 000000000000..7d1ffbc389ae --- /dev/null +++ b/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js @@ -0,0 +1,79 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Check that nsIConsoleMessages are displayed in the Browser Console. +// See bug 859756. + +const TEST_URI = "data:text/html;charset=utf8,bug859756\n" + + "

hello world\n

nsIConsoleMessages ftw!"; + +function test() +{ + addTab(TEST_URI); + browser.addEventListener("load", function onLoad() { + browser.removeEventListener("load", onLoad, true); + + // Test for cached nsIConsoleMessages. + Services.console.logStringMessage("test1 for bug859756"); + + info("open web console"); + openConsole(null, consoleOpened); + }, true); +} + +function consoleOpened(hud) +{ + ok(hud, "web console opened"); + Services.console.logStringMessage("do-not-show-me"); + content.console.log("foobarz"); + + waitForMessages({ + webconsole: hud, + messages: [ + { + text: "foobarz", + category: CATEGORY_WEBDEV, + severity: SEVERITY_LOG, + }, + ], + }).then(() => { + let text = hud.outputNode.textContent; + is(text.indexOf("do-not-show-me"), -1, + "nsIConsoleMessages are not displayed"); + is(text.indexOf("test1 for bug859756"), -1, + "nsIConsoleMessages are not displayed (confirmed)"); + closeConsole(null, onWebConsoleClose); + }); +} + +function onWebConsoleClose() +{ + info("web console closed"); + HUDConsoleUI.toggleBrowserConsole().then(onBrowserConsoleOpen); +} + +function onBrowserConsoleOpen(hud) +{ + ok(hud, "browser console opened"); + Services.console.logStringMessage("test2 for bug859756"); + + waitForMessages({ + webconsole: hud, + messages: [ + { + text: "test1 for bug859756", + category: CATEGORY_JS, + }, + { + text: "test2 for bug859756", + category: CATEGORY_JS, + }, + { + text: "do-not-show-me", + category: CATEGORY_JS, + }, + ], + }).then(finishTest); +} diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index 258f3cbc1c6c..e9bd387068aa 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -961,6 +961,9 @@ WebConsoleFrame.prototype = { [category, aMessage]); break; } + case "LogMessage": + this.handleLogMessage(aMessage); + break; case "ConsoleAPI": this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]); @@ -1198,6 +1201,21 @@ WebConsoleFrame.prototype = { this.outputMessage(category, this.reportPageError, [category, aPageError]); }, + /** + * Handle log messages received from the server. This method outputs the given + * message. + * + * @param object aPacket + * The message packet received from the server. + */ + handleLogMessage: function WCF_handleLogMessage(aPacket) + { + this.outputMessage(CATEGORY_JS, () => { + return this.createMessageNode(CATEGORY_JS, SEVERITY_LOG, aPacket.message, + null, null, null, null, aPacket.timeStamp); + }); + }, + /** * Log network event. * @@ -4530,6 +4548,7 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget) this.target = aTarget; this._onPageError = this._onPageError.bind(this); + this._onLogMessage = this._onLogMessage.bind(this); this._onConsoleAPICall = this._onConsoleAPICall.bind(this); this._onNetworkEvent = this._onNetworkEvent.bind(this); this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); @@ -4634,6 +4653,7 @@ WebConsoleConnectionProxy.prototype = { let client = this.client = this.target.client; + client.addListener("logMessage", this._onLogMessage); client.addListener("pageError", this._onPageError); client.addListener("consoleAPICall", this._onConsoleAPICall); client.addListener("networkEvent", this._onNetworkEvent); @@ -4755,6 +4775,23 @@ WebConsoleConnectionProxy.prototype = { } }, + /** + * The "logMessage" message type handler. We redirect any message to the UI + * for displaying. + * + * @private + * @param string aType + * Message type. + * @param object aPacket + * The message received from the server. + */ + _onLogMessage: function WCCP__onLogMessage(aType, aPacket) + { + if (this.owner && aPacket.from == this._consoleActor) { + this.owner.handleLogMessage(aPacket); + } + }, + /** * The "consoleAPICall" message type handler. We redirect any message to * the UI for displaying. @@ -4899,6 +4936,7 @@ WebConsoleConnectionProxy.prototype = { return this._disconnecter.promise; } + this.client.removeListener("logMessage", this._onLogMessage); this.client.removeListener("pageError", this._onPageError); this.client.removeListener("consoleAPICall", this._onConsoleAPICall); this.client.removeListener("networkEvent", this._onNetworkEvent); diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index cb7f2b5e61fd..73d2251d4594 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -214,11 +214,12 @@ These should match what Safari and other Apple applications use on OS X Lion. -- - + + diff --git a/browser/metro/base/content/bindings/bindings.xml b/browser/metro/base/content/bindings/bindings.xml index 9651d58fcf14..f3b660ab5a72 100644 --- a/browser/metro/base/content/bindings/bindings.xml +++ b/browser/metro/base/content/bindings/bindings.xml @@ -151,25 +151,6 @@ document.getAnonymousElementByAttribute(this, "anonid", "close-button"); - - - - null @@ -192,7 +173,6 @@ this._nextButton.disabled = document.getElementById(aModel.commands.next).getAttribute("disabled") == "true"; this.model = aModel; - this.contentHasChanged(); ]]> @@ -204,7 +184,6 @@ this.removeAttribute("close"); this.removeAttribute("type"); - this.contentHasChanged(); this.model = null; ]]> diff --git a/browser/metro/base/content/browser-ui.js b/browser/metro/base/content/browser-ui.js index d841011ae3cd..5c19f634daba 100644 --- a/browser/metro/base/content/browser-ui.js +++ b/browser/metro/base/content/browser-ui.js @@ -594,8 +594,6 @@ var BrowserUI = { } Elements.windowState.setAttribute("viewstate", currViewState); } - // content navigator helper - document.getElementById("content-navigator").contentHasChanged(); }, _titleChanged: function(aBrowser) { @@ -761,7 +759,7 @@ var BrowserUI = { } // Check content helper - let contentHelper = document.getElementById("content-navigator"); + let contentHelper = Elements.contentNavigator; if (contentHelper.isActive) { contentHelper.model.hide(); return; diff --git a/browser/metro/base/content/browser.js b/browser/metro/base/content/browser.js index 828588dab347..a348a38ddbcd 100644 --- a/browser/metro/base/content/browser.js +++ b/browser/metro/base/content/browser.js @@ -619,9 +619,9 @@ var Browser = { let sslExceptions = new SSLExceptions(); if (json.action == "permanent") - sslExceptions.addPermanentException(uri, errorDoc.defaultView); + sslExceptions.addPermanentException(uri, window); else - sslExceptions.addTemporaryException(uri, errorDoc.defaultView); + sslExceptions.addTemporaryException(uri, window); } catch (e) { dump("EXCEPTION handle content command: " + e + "\n" ); } diff --git a/browser/metro/base/content/helperui/FindHelperUI.js b/browser/metro/base/content/helperui/FindHelperUI.js index 4a21086ece2b..82d9d915b3da 100644 --- a/browser/metro/base/content/helperui/FindHelperUI.js +++ b/browser/metro/base/content/helperui/FindHelperUI.js @@ -39,7 +39,7 @@ var FindHelperUI = { init: function findHelperInit() { this._textbox = document.getElementById("find-helper-textbox"); - this._container = document.getElementById("content-navigator"); + this._container = Elements.contentNavigator; this._cmdPrevious = document.getElementById(this.commands.previous); this._cmdNext = document.getElementById(this.commands.next); @@ -50,10 +50,6 @@ var FindHelperUI = { messageManager.addMessageListener("FindAssist:Show", this); messageManager.addMessageListener("FindAssist:Hide", this); - // Listen for pan events happening on the browsers - Elements.browsers.addEventListener("PanBegin", this, false); - Elements.browsers.addEventListener("PanFinished", this, false); - // Listen for events where form assistant should be closed Elements.tabList.addEventListener("TabSelect", this, true); Elements.browsers.addEventListener("URLChanged", this, true); @@ -91,16 +87,6 @@ var FindHelperUI = { this.hide(); break; - case "PanBegin": - this._container.style.visibility = "hidden"; - this._textbox.collapsed = true; - break; - - case "PanFinished": - this._container.style.visibility = "visible"; - this._textbox.collapsed = false; - break; - case "keydown": if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) { if (aEvent.shiftKey) { @@ -113,6 +99,9 @@ var FindHelperUI = { }, show: function findHelperShow() { + if (this._open) + return; + // Hide any menus ContextUI.dismiss(); @@ -124,6 +113,8 @@ var FindHelperUI = { this._textbox.select(); this._textbox.focus(); this._open = true; + Elements.browsers.setAttribute("findbar", true); + setTimeout(() => this._container.setAttribute("showing", true), 0); // Prevent the view to scroll automatically while searching Browser.selectedBrowser.scrollSync = false; @@ -133,14 +124,21 @@ var FindHelperUI = { if (!this._open) return; - this._textbox.value = ""; - this.status = null; - this._textbox.blur(); - this._container.hide(this); - this._open = false; + let onTransitionEnd = () => { + this._container.removeEventListener("transitionend", onTransitionEnd, true); + this._textbox.value = ""; + this.status = null; + this._textbox.blur(); + this._container.hide(this); + this._open = false; - // Restore the scroll synchronisation - Browser.selectedBrowser.scrollSync = true; + // Restore the scroll synchronisation + Browser.selectedBrowser.scrollSync = true; + }; + + this._container.addEventListener("transitionend", onTransitionEnd, true); + this._container.removeAttribute("showing"); + Elements.browsers.removeAttribute("findbar"); }, goToPrevious: function findHelperGoToPrevious() { diff --git a/browser/metro/base/content/helperui/MenuUI.js b/browser/metro/base/content/helperui/MenuUI.js index 41cd3afdaf65..a937544b9cbd 100644 --- a/browser/metro/base/content/helperui/MenuUI.js +++ b/browser/metro/base/content/helperui/MenuUI.js @@ -194,7 +194,7 @@ var ContextMenuUI = { // chrome calls don't need to be translated and as such // don't provide target. - if (aMessage.target) { + if (aMessage.target && aMessage.target.localName === "browser") { coords = aMessage.target.msgBrowserToClient(aMessage, true); } this._menuPopup.show(Util.extend({}, this._defaultPositionOptions, { diff --git a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp index 7029df2bd4fa..28037e73ec11 100644 --- a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp +++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp @@ -37,6 +37,9 @@ static const WCHAR* kFirefoxExe = L"firefox.exe"; static const WCHAR* kMetroFirefoxExe = L"firefox.exe"; static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; +static bool GetDesktopBrowserPath(CStringW& aPathBuffer); +static bool GetDefaultBrowserPath(CStringW& aPathBuffer); + template void SafeRelease(T **ppT) { if (*ppT) { @@ -278,38 +281,49 @@ public: bool IsDefaultBrowser() { - bool result = false; IApplicationAssociationRegistration* pAAR; HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC, IID_IApplicationAssociationRegistration, (void**)&pAAR); - if (SUCCEEDED(hr)) { - BOOL res; - hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, - APP_REG_NAME, - &res); - Log(L"QueryAppIsDefaultAll: %d", res); - if (!res) - return false; - // Make sure the Prog ID matches what we have - LPWSTR registeredApp; - hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE, - ®isteredApp); - Log(L"QueryCurrentDefault: %X", hr); - if (SUCCEEDED(hr)) { - Log(L"registeredApp=%s", registeredApp); - result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey); - CoTaskMemFree(registeredApp); - } else { - result = false; - } + if (FAILED(hr)) + return false; + BOOL res = FALSE; + hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, + APP_REG_NAME, + &res); + Log(L"QueryAppIsDefaultAll: %d", res); + if (!res) { pAAR->Release(); - return result; + return false; } - return result; + // Make sure the Prog ID matches what we have + LPWSTR registeredApp; + hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE, + ®isteredApp); + pAAR->Release(); + Log(L"QueryCurrentDefault: %X", hr); + if (FAILED(hr)) + return false; + + Log(L"registeredApp=%s", registeredApp); + bool result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey); + CoTaskMemFree(registeredApp); + if (!result) + return false; + + // If the registry points another browser's path, + // activating the Metro browser will fail. So fallback to the desktop. + CStringW selfPath; + GetDesktopBrowserPath(selfPath); + selfPath.MakeLower(); + CStringW browserPath; + GetDefaultBrowserPath(browserPath); + browserPath.MakeLower(); + + return selfPath == browserPath; } private: ~CExecuteCommandVerb() @@ -375,6 +389,32 @@ static bool GetDesktopBrowserPath(CStringW& aPathBuffer) return true; } +/* + * Retrieve the current default browser's path. + * + * @aPathBuffer Buffer to fill + */ +static bool GetDefaultBrowserPath(CStringW& aPathBuffer) +{ + WCHAR buffer[MAX_PATH]; + DWORD length = MAX_PATH; + + if (FAILED(AssocQueryStringW(ASSOCF_NOTRUNCATE | ASSOCF_INIT_IGNOREUNKNOWN, + ASSOCSTR_EXECUTABLE, + kDefaultMetroBrowserIDPathKey, NULL, + buffer, &length))) { + Log(L"AssocQueryString failed."); + return false; + } + + // sanity check + if (lstrcmpiW(PathFindFileNameW(buffer), kFirefoxExe)) + return false; + + aPathBuffer = buffer; + return true; +} + /* * Retrieve the app model id of the firefox metro browser. * @@ -540,6 +580,8 @@ void CExecuteCommandVerb::LaunchDesktopBrowser() // be the browser exe or file. CStringW params; if (!IsTargetBrowser()) { + // Fallback to the module path if it failed to get the default browser. + GetDefaultBrowserPath(browserPath); params += "-url "; params += "\""; params += mTarget; diff --git a/browser/metro/theme/browser.css b/browser/metro/theme/browser.css index d2dc4e38d6a1..e7f066cefac6 100644 --- a/browser/metro/theme/browser.css +++ b/browser/metro/theme/browser.css @@ -733,6 +733,14 @@ setting[type="radio"] > vbox { transition-timing-function: ease-in-out; } +#browsers browser { + transition: padding-bottom @metro_animation_duration@ @metro_animation_easing@; +} + +#browsers[findbar] browser { + padding-bottom: @findbar_height@; +} + /* Panel UI ---------------------------------------------------------------- */ #panel-container { diff --git a/browser/metro/theme/defines.inc b/browser/metro/theme/defines.inc index 134a061784d3..f77c3fe65825 100644 --- a/browser/metro/theme/defines.inc +++ b/browser/metro/theme/defines.inc @@ -21,6 +21,7 @@ %define toolbar_horizontal_spacing 20px %define toolbar_height 68px %define tabs_height 178px +%define findbar_height 54px %define progress_height 5px diff --git a/browser/metro/theme/forms.css b/browser/metro/theme/forms.css index 9dab3c58b6b9..753ff6b466f8 100644 --- a/browser/metro/theme/forms.css +++ b/browser/metro/theme/forms.css @@ -13,6 +13,12 @@ background-color: @metro_orange@; bottom: 0; position: fixed; + transition: margin-bottom @metro_animation_duration@ @metro_animation_easing@; + margin-bottom: -@findbar_height@; +} + +#content-navigator[showing] { + margin-bottom: 0; } #content-navigator[type="find"], diff --git a/build/automation.py.in b/build/automation.py.in index c5556dfd77d8..29a57daeeb72 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -756,7 +756,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t # 841150, and 839677 (at least) # Also (temporary) bug 870002 (mediastreamgraph) if 'NSPR_LOG_MODULES' not in env: - env['NSPR_LOG_MODULES'] = 'signaling:5,mtransport:3,mediastreamgraph:4' + env['NSPR_LOG_MODULES'] = 'signaling:5,mtransport:3' env['R_LOG_LEVEL'] = '5' env['R_LOG_DESTINATION'] = 'stderr' env['R_LOG_VERBOSE'] = '1' diff --git a/build/mobile/sutagent/android/ASMozStub.java b/build/mobile/sutagent/android/ASMozStub.java index fec2399cb915..7ecf2f2e0859 100755 --- a/build/mobile/sutagent/android/ASMozStub.java +++ b/build/mobile/sutagent/android/ASMozStub.java @@ -76,8 +76,9 @@ public class ASMozStub extends android.app.Service { mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature); } catch (NoSuchMethodException e) { - // Running on an older platform. + // Might be running on an older platform. mStartForeground = mStopForeground = null; + Log.w("SUTAgent", "unable to find start/stopForeground method(s) -- older platform?"); } try { @@ -85,6 +86,7 @@ public class ASMozStub extends android.app.Service { } catch (NoSuchMethodException e) { mSetForeground = null; + Log.e("SUTAgent", "unable to find setForeground method!"); } doToast("Listener Service created..."); @@ -256,10 +258,10 @@ public class ASMozStub extends android.app.Service { mStartForeground.invoke(this, mStartForegroundArgs); } catch (InvocationTargetException e) { // Should not happen. - Log.w("ScreenOnWidget", "Unable to invoke startForeground", e); + Log.e("SUTAgent", "Unable to invoke startForeground", e); } catch (IllegalAccessException e) { // Should not happen. - Log.w("ScreenOnWidget", "Unable to invoke startForeground", e); + Log.e("SUTAgent", "Unable to invoke startForeground", e); } return; } @@ -270,10 +272,13 @@ public class ASMozStub extends android.app.Service { mSetForegroundArgs[0] = Boolean.TRUE; mSetForeground.invoke(this, mSetForegroundArgs); } catch (IllegalArgumentException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } catch (IllegalAccessException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } catch (InvocationTargetException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } } @@ -292,10 +297,10 @@ public class ASMozStub extends android.app.Service { mStopForeground.invoke(this, mStopForegroundArgs); } catch (InvocationTargetException e) { // Should not happen. - Log.w("ScreenOnWidget", "Unable to invoke stopForeground", e); + Log.e("SUTAgent", "Unable to invoke stopForeground", e); } catch (IllegalAccessException e) { // Should not happen. - Log.w("ScreenOnWidget", "Unable to invoke stopForeground", e); + Log.e("SUTAgent", "Unable to invoke stopForeground", e); } return; } @@ -308,10 +313,13 @@ public class ASMozStub extends android.app.Service { mSetForegroundArgs[0] = Boolean.FALSE; mSetForeground.invoke(this, mSetForegroundArgs); } catch (IllegalArgumentException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } catch (IllegalAccessException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } catch (InvocationTargetException e) { + Log.e("SUTAgent", "Unable to invoke setForeground", e); e.printStackTrace(); } } diff --git a/build/mobile/sutagent/android/DoCommand.java b/build/mobile/sutagent/android/DoCommand.java index efdb62fbecbb..6d44ef583fd9 100755 --- a/build/mobile/sutagent/android/DoCommand.java +++ b/build/mobile/sutagent/android/DoCommand.java @@ -107,7 +107,7 @@ public class DoCommand { String ffxProvider = "org.mozilla.ffxcp"; String fenProvider = "org.mozilla.fencp"; - private final String prgVersion = "SUTAgentAndroid Version 1.17"; + private final String prgVersion = "SUTAgentAndroid Version 1.18"; public enum Command { @@ -124,6 +124,7 @@ public class DoCommand { ID ("id"), UPTIME ("uptime"), UPTIMEMILLIS ("uptimemillis"), + SUTUPTIMEMILLIS ("sutuptimemillis"), SETTIME ("settime"), SYSTIME ("systime"), SCREEN ("screen"), @@ -429,6 +430,8 @@ public class DoCommand { strReturn += "\n"; strReturn += GetUptimeMillis(); strReturn += "\n"; + strReturn += GetSutUptimeMillis(); + strReturn += "\n"; strReturn += GetScreenInfo(); strReturn += "\n"; strReturn += GetRotationInfo(); @@ -486,6 +489,10 @@ public class DoCommand { strReturn = GetUptimeMillis(); break; + case SUTUPTIMEMILLIS: + strReturn = GetSutUptimeMillis(); + break; + case MEMORY: strReturn = GetMemoryInfo(); break; @@ -3066,6 +3073,12 @@ private void CancelNotification() return Long.toString(SystemClock.uptimeMillis()); } + public String GetSutUptimeMillis() + { + long now = System.currentTimeMillis(); + return "SUTagent running for "+Long.toString(now - SUTAgentAndroid.nCreateTimeMillis)+" ms"; + } + public String NewKillProc(String sProcId, OutputStream out) { String sRet = ""; @@ -3889,6 +3902,7 @@ private void CancelNotification() " [id] - unique identifier for device\n" + " [uptime] - uptime for device\n" + " [uptimemillis] - uptime for device in milliseconds\n" + + " [sutuptimemillis] - uptime for SUT in milliseconds\n" + " [systime] - current system time\n" + " [screen] - width, height and bits per pixel for device\n" + " [memory] - physical, free, available, storage memory\n" + diff --git a/build/mobile/sutagent/android/SUTAgentAndroid.java b/build/mobile/sutagent/android/SUTAgentAndroid.java index d032c992bed2..ac09f63cd216 100755 --- a/build/mobile/sutagent/android/SUTAgentAndroid.java +++ b/build/mobile/sutagent/android/SUTAgentAndroid.java @@ -66,6 +66,7 @@ public class SUTAgentAndroid extends Activity public static String sPowerStatus = null; public static int nChargeLevel = 0; public static int nBatteryTemp = 0; + public static long nCreateTimeMillis = System.currentTimeMillis(); String lineSep = System.getProperty("line.separator"); public PrintWriter dataOut = null; @@ -395,14 +396,12 @@ public class SUTAgentAndroid extends Activity } } - @Override - public void onLowMemory() + private void logMemory(String caller) { - System.gc(); DoCommand dc = new DoCommand(getApplication()); if (dc != null) { - log(dc, "onLowMemory"); + log(dc, caller); log(dc, dc.GetMemoryInfo()); String procInfo = dc.GetProcessInfo(); if (procInfo != null) @@ -424,10 +423,24 @@ public class SUTAgentAndroid extends Activity } else { - Log.e("SUTAgentAndroid", "onLowMemory: unable to log to file!"); + Log.e("SUTAgentAndroid", "logMemory: unable to log to file!"); } } + @Override + public void onLowMemory() + { + System.gc(); + logMemory("onLowMemory"); + } + + @Override + public void onTrimMemory(int level) + { + System.gc(); + logMemory("onTrimMemory"+level); + } + private void monitorBatteryState() { battReceiver = new BroadcastReceiver() diff --git a/build/mobile/sutagent/android/watcher/WatcherService.java b/build/mobile/sutagent/android/watcher/WatcherService.java index c0c79f7e4c71..4b5554256926 100644 --- a/build/mobile/sutagent/android/watcher/WatcherService.java +++ b/build/mobile/sutagent/android/watcher/WatcherService.java @@ -49,7 +49,8 @@ import android.os.Environment; public class WatcherService extends Service { - private final String prgVersion = "Watcher Version 1.15"; + private final String prgVersion = "Watcher Version 1.16"; + private final String LOGTAG = "Watcher"; String sErrorPrefix = "##Installer Error## "; String currentDir = "/"; @@ -60,7 +61,7 @@ public class WatcherService extends Service boolean bStartSUTAgent = true; boolean bStartedTimer = false; - Process pProc; + Process pProc; Context myContext = null; Timer myTimer = null; private PowerManager.WakeLock pwl = null; @@ -69,12 +70,12 @@ public class WatcherService extends Service @SuppressWarnings("unchecked") private static final Class[] mSetForegroundSignature = new Class[] { - boolean.class}; + boolean.class}; @SuppressWarnings("unchecked") - private static final Class[] mStartForegroundSignature = new Class[] { + private static final Class[] mStartForegroundSignature = new Class[] { int.class, Notification.class}; @SuppressWarnings("unchecked") - private static final Class[] mStopForegroundSignature = new Class[] { + private static final Class[] mStopForegroundSignature = new Class[] { boolean.class}; private NotificationManager mNM; @@ -103,6 +104,8 @@ public class WatcherService extends Service { super.onCreate(); + Log.i(LOGTAG, prgVersion); + myContext = this; getKeyGuardAndWakeLock(); @@ -112,7 +115,7 @@ public class WatcherService extends Service String sIniFile = iniFile.getAbsolutePath(); String sHold = ""; - Log.i("Watcher", String.format("Loading settings from %s", sIniFile)); + Log.i(LOGTAG, String.format("Loading settings from %s", sIniFile)); this.sPingTarget = GetIniData("watcher", "PingTarget", sIniFile, "www.mozilla.org"); sHold = GetIniData("watcher", "delay", sIniFile, "60000"); this.lDelay = Long.parseLong(sHold.trim()); @@ -120,8 +123,8 @@ public class WatcherService extends Service this.lPeriod = Long.parseLong(sHold.trim()); sHold = GetIniData("watcher", "strikes", sIniFile,"0"); this.nMaxStrikes = Integer.parseInt(sHold.trim()); - Log.i("Watcher", String.format("Pinging %s after a delay of %s sec, period of %s sec, max number of failed attempts is %s (if max # of failed attempts is 0, then no checking)", - this.sPingTarget, this.lDelay / 1000.0, this.lPeriod / 1000.0, nMaxStrikes)); + Log.i(LOGTAG, String.format("Pinging %s after a delay of %s sec, period of %s sec, max number of failed attempts is %s (if max # of failed attempts is 0, then no checking)", + this.sPingTarget, this.lDelay / 1000.0, this.lPeriod / 1000.0, nMaxStrikes)); sHold = GetIniData("watcher", "StartSUTAgent", sIniFile, "true"); this.bStartSUTAgent = Boolean.parseBoolean(sHold.trim()); @@ -151,7 +154,7 @@ public class WatcherService extends Service String sLine = ""; boolean bFound = false; BufferedReader in = null; - String sTmpFileName = fixFileName(sFile); + String sTmpFileName = fixFileName(sFile); try { in = new BufferedReader(new FileReader(sTmpFileName)); @@ -217,19 +220,20 @@ public class WatcherService extends Service String sOutFile = intent.getStringExtra("outFile"); boolean bReboot = intent.getBooleanExtra("reboot", true); int nReboot = bReboot ? 1 : 0; - SendNotification("WatcherService updating " + sPkgName + " using file " + sPkgFile, "WatcherService updating " + sPkgName + " using file " + sPkgFile); + Log.i(LOGTAG, "WatcherService updating " + sPkgName + " using file " + sPkgFile); - UpdateApplication worker = new UpdateApplication(sPkgName, sPkgFile, sOutFile, nReboot); + UpdateApplication worker = new UpdateApplication(sPkgName, sPkgFile, sOutFile, nReboot); } else if (sCmd.equalsIgnoreCase("start")) { - if (!this.bStartedTimer) { + if (!this.bStartedTimer) + { doToast("WatcherService started"); myTimer = new Timer(); Date startSchedule = new Date(System.currentTimeMillis() + lDelay); myTimer.schedule(new MyTime(), startSchedule, lPeriod); this.bStartedTimer = true; - } + } } else { @@ -244,12 +248,12 @@ public class WatcherService extends Service PrintWriter pw = null; String appPath = getApplicationContext().getFilesDir().getAbsolutePath(); String versionPath = appPath + "/version.txt"; - Log.i("Watcher", "writing version string to: " + versionPath); + Log.i(LOGTAG, "writing version string to: " + versionPath); try { pw = new PrintWriter(new FileWriter(versionPath, true)); pw.println(this.prgVersion); } catch (IOException ioe) { - Log.e("Watcher", "Exception writing version: " + this.prgVersion + " to file: " + versionPath); + Log.e(LOGTAG, "Exception writing version: " + this.prgVersion + " to file: " + versionPath); } finally { if (pw != null) { pw.close(); @@ -259,6 +263,7 @@ public class WatcherService extends Service @Override public void onStart(Intent intent, int startId) { + Log.i(LOGTAG, "onStart"); writeVersion(); handleCommand(intent); return; @@ -266,10 +271,11 @@ public class WatcherService extends Service @Override public int onStartCommand(Intent intent, int flags, int startId) { + Log.i(LOGTAG, "onStartCommand"); writeVersion(); handleCommand(intent); return START_STICKY; - } + } @Override public void onDestroy() { @@ -280,18 +286,34 @@ public class WatcherService extends Service stopForegroundCompat(R.string.foreground_service_started); } + @Override + public void onLowMemory() { + Log.e(LOGTAG, "onLowMemory"); + System.gc(); + } + + @Override + public void onTrimMemory(int level) { + Log.e(LOGTAG, "onTrimMemory: "+level); + System.gc(); + } + protected void getKeyGuardAndWakeLock() { // Fire off a thread to do some work that we shouldn't do directly in the UI thread Thread t = new Thread() { public void run() { + Log.i(LOGTAG, "worker thread started"); // Keep phone from locking or remove lock on screen KeyguardManager km = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); if (km != null) { KeyguardManager.KeyguardLock kl = km.newKeyguardLock("watcher"); if (kl != null) + { kl.disableKeyguard(); + Log.i(LOGTAG, "keyguard disabled"); + } } // No sleeping on the job @@ -300,24 +322,39 @@ public class WatcherService extends Service { pwl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "watcher"); if (pwl != null) + { pwl.acquire(); + Log.i(LOGTAG, "wake lock acquired"); + } } + Class serviceClass = null; + try { + serviceClass = Class.forName("com.mozilla.watcher.WatcherService"); + } + catch (Exception e) + { + Log.e(LOGTAG, "unable to find service class: "+e.toString()); + return; + } mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); try { - mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature); - mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature); + mStartForeground = serviceClass.getMethod("startForeground", mStartForegroundSignature); + mStopForeground = serviceClass.getMethod("stopForeground", mStopForegroundSignature); } catch (NoSuchMethodException e) { - // Running on an older platform. + // Might be running on an older platform. mStartForeground = mStopForeground = null; + Log.w(LOGTAG, "unable to find start/stopForeground method(s) -- older platform?"); } try { - mSetForeground = getClass().getMethod("setForeground", mSetForegroundSignature); + mSetForeground = serviceClass.getMethod("setForeground", mSetForegroundSignature); } - catch (NoSuchMethodException e) { + catch (NoSuchMethodException e) + { mSetForeground = null; + Log.e(LOGTAG, "unable to find setForeground method!"); } Notification notification = new Notification(); startForegroundCompat(R.string.foreground_service_started, notification); @@ -337,12 +374,13 @@ public class WatcherService extends Service mStartForegroundArgs[1] = notification; try { mStartForeground.invoke(this, mStartForegroundArgs); + Log.i(LOGTAG, "startForeground invoked"); } catch (InvocationTargetException e) { // Should not happen. - Log.w("ApiDemos", "Unable to invoke startForeground", e); + Log.e(LOGTAG, "Unable to invoke startForeground", e); } catch (IllegalAccessException e) { // Should not happen. - Log.w("ApiDemos", "Unable to invoke startForeground", e); + Log.e(LOGTAG, "Unable to invoke startForeground", e); } return; } @@ -352,11 +390,15 @@ public class WatcherService extends Service try { mSetForegroundArgs[0] = Boolean.TRUE; mSetForeground.invoke(this, mSetForegroundArgs); + Log.i(LOGTAG, "setForeground(TRUE) invoked"); } catch (IllegalArgumentException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } catch (IllegalAccessException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } catch (InvocationTargetException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } } @@ -373,12 +415,13 @@ public class WatcherService extends Service mStopForegroundArgs[0] = Boolean.TRUE; try { mStopForeground.invoke(this, mStopForegroundArgs); + Log.i(LOGTAG, "stopForeground invoked"); } catch (InvocationTargetException e) { // Should not happen. - Log.w("ApiDemos", "Unable to invoke stopForeground", e); + Log.e(LOGTAG, "Unable to invoke stopForeground", e); } catch (IllegalAccessException e) { // Should not happen. - Log.w("ApiDemos", "Unable to invoke stopForeground", e); + Log.e(LOGTAG, "Unable to invoke stopForeground", e); } return; } @@ -390,11 +433,15 @@ public class WatcherService extends Service try { mSetForegroundArgs[0] = Boolean.FALSE; mSetForeground.invoke(this, mSetForegroundArgs); + Log.i(LOGTAG, "setForeground(FALSE) invoked"); } catch (IllegalArgumentException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } catch (IllegalAccessException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } catch (InvocationTargetException e) { + Log.e(LOGTAG, "Unable to invoke setForeground", e); e.printStackTrace(); } } @@ -402,20 +449,21 @@ public class WatcherService extends Service public void doToast(String sMsg) { - Log.i("Watcher", sMsg); + Log.i(LOGTAG, sMsg); Toast toast = Toast.makeText(this, sMsg, Toast.LENGTH_LONG); toast.setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL, 0, 100); toast.show(); } - public void CheckMem() { - System.gc(); + public void CheckMem() + { + System.gc(); long lFreeMemory = Runtime.getRuntime().freeMemory(); long lTotMemory = Runtime.getRuntime().totalMemory(); long lMaxMemory = Runtime.getRuntime().maxMemory(); - SendNotification("Memory Check", "Free: " + lFreeMemory + "Total: " + lTotMemory + "Max: " + lMaxMemory); - } + Log.i(LOGTAG, "Free: " + lFreeMemory + "Total: " + lTotMemory + "Max: " + lMaxMemory); + } public int UpdtApp(String sPkgName, String sPkgFileName, String sOutFile, int bReboot) { @@ -423,45 +471,43 @@ public class WatcherService extends Service int lcv = 0; String sRet = ""; -// Debug.waitForDebugger(); - FileOutputStream f = null; - try { - SendNotification("Killing " + sPkgName, "Step 1: Kill " + sPkgName + " if running"); + try { + Log.i(LOGTAG, "Step 1: Kill " + sPkgName + " if running"); while (!IsProcessDead(sPkgName) && (lcv < 5)) { if (KillProcess(sPkgName, null).startsWith("Successfully")) break; else lcv++; - Thread.sleep(2000); - } + Thread.sleep(2000); + } - CheckMem(); + CheckMem(); if ((sOutFile != null) && (sOutFile.length() > 0)) { File outFile = new File(sOutFile); if (outFile.exists() && outFile.canWrite()) { f = new FileOutputStream(outFile, true); } else { - SendNotification("File not found or cannot write to " + sOutFile, "File not found or cannot write to " + sOutFile); + Log.e(LOGTAG, "File not found or cannot write to " + sOutFile); } } } catch (InterruptedException e) { - e.printStackTrace(); + e.printStackTrace(); } catch (FileNotFoundException e) { - SendNotification("File not found " + sOutFile, "Couldn't open " + sOutFile + " " + e.getLocalizedMessage()); + Log.e(LOGTAG, "Couldn't open " + sOutFile + " " + e.getLocalizedMessage()); e.printStackTrace(); - } catch (SecurityException e) { - SendNotification("Security excepetion for " + sOutFile, "Exception message " + e.getLocalizedMessage()); + } catch (SecurityException e) { + Log.e(LOGTAG, "Exception message " + e.getLocalizedMessage()); e.printStackTrace(); - } + } if ((sPkgName != null) && (sPkgName.length() > 0)) { - SendNotification("Uninstalling " + sPkgName, "Step 2: Uninstall " + sPkgName); + Log.i(LOGTAG, "Step 2: Uninstall " + sPkgName); sRet = UnInstallApp(sPkgName, null); - CheckMem(); + CheckMem(); if ((sRet.length() > 0) && (f != null)) { try { @@ -477,10 +523,10 @@ public class WatcherService extends Service if ((sPkgFileName != null) && (sPkgFileName.length() > 0)) { - SendNotification("Installing " + sPkgFileName, "Step 3: Install " + sPkgFileName); + Log.i(LOGTAG, "Step 3: Install " + sPkgFileName); sRet = InstallApp(sPkgFileName, null); - SendNotification("Installed " + sPkgFileName, "" + sRet); - CheckMem(); + Log.i(LOGTAG, "" + sRet); + CheckMem(); if ((sRet.length() > 0) && (f != null)) { try { @@ -533,7 +579,7 @@ public class WatcherService extends Service theArgs[0] = "su"; theArgs[1] = "-c"; theArgs[2] = "reboot"; - Log.i("Watcher", "Running reboot!"); + Log.i(LOGTAG, "Running reboot!"); try { @@ -568,7 +614,7 @@ public class WatcherService extends Service List lProcesses = aMgr.getRunningAppProcesses(); int lcv = 0; String strProcName = ""; - int nPID = 0; + int nPID = 0; int nProcs = 0; if (lProcesses != null) @@ -698,22 +744,6 @@ public class WatcherService extends Service return(sRet); } - public String GetTmpDir() - { - String sRet = ""; - Context ctx = getApplicationContext(); - File dir = ctx.getFilesDir(); - ctx = null; - try { - sRet = dir.getCanonicalPath(); - } - catch (IOException e) - { - e.printStackTrace(); - } - return(sRet); - } - public String UnInstallApp(String sApp, OutputStream out) { String sRet = ""; @@ -782,11 +812,11 @@ public class WatcherService extends Service private String SendPing(String sIPAddr) { - Process pProc; + Process pProc; String sRet = ""; String [] theArgs = new String [4]; boolean bStillRunning = true; - int nBytesOut = 0; + int nBytesOut = 0; int nBytesErr = 0; int nBytesRead = 0; byte[] buffer = new byte[1024]; @@ -795,7 +825,7 @@ public class WatcherService extends Service theArgs[1] = "-c"; theArgs[2] = "3"; theArgs[3] = sIPAddr; - Log.i("Watcher", "Pinging " + sIPAddr); + Log.i(LOGTAG, "Pinging " + sIPAddr); try { @@ -872,7 +902,7 @@ public class WatcherService extends Service e.printStackTrace(); } - Log.i("Watcher", String.format("Ping result was: '%s'", sRet.trim())); + Log.i(LOGTAG, String.format("Ping result was: '%s'", sRet.trim())); return (sRet); } @@ -903,7 +933,7 @@ public class WatcherService extends Service String msPkgName = ""; String msPkgFileName = ""; String msOutFile = ""; - int mbReboot = 0; + int mbReboot = 0; public UpdateApplication(String sPkgName, String sPkgFileName, String sOutFile, int bReboot) { runner = new Thread(this); @@ -915,9 +945,9 @@ public class WatcherService extends Service } public void run() { - bInstalling = true; + bInstalling = true; UpdtApp(msPkgName, msPkgFileName, msOutFile, mbReboot); - bInstalling = false; + bInstalling = false; } } @@ -944,11 +974,11 @@ public class WatcherService extends Service String sRet = SendPing(sPingTarget); if (!sRet.contains("3 received")) { - Log.i("Watcher", String.format("Failed ping attempt (remaining: %s)!", - nMaxStrikes - nStrikes)); + Log.i(LOGTAG, String.format("Failed ping attempt (remaining: %s)!", + nMaxStrikes - nStrikes)); if (++nStrikes >= nMaxStrikes) { - Log.e("Watcher", String.format("Number of failed ping attempts to %s (%s) exceeded maximum (%s), running reboot!", sPingTarget, nStrikes, nMaxStrikes)); + Log.e(LOGTAG, String.format("Number of failed ping attempts to %s (%s) exceeded maximum (%s), running reboot!", sPingTarget, nStrikes, nMaxStrikes)); RunReboot(null); } } @@ -960,18 +990,16 @@ public class WatcherService extends Service String sProgramName = "com.mozilla.SUTAgentAndroid"; -// Debug.waitForDebugger(); - // Ensure the sdcard is mounted before we even attempt to start the agent // We will wait for the sdcard to mount for PERIODS_TO_WAIT_FOR_SDCARD // after which time we go ahead and attempt to start the agent. if (nPeriodsWaited++ < PERIODS_TO_WAIT_FOR_SDCARD) { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.compareTo(state) != 0) { - Log.i("SUTAgentWatcher", "SDcard not mounted, waiting another turn"); + Log.i(LOGTAG, "SDcard not mounted, waiting another turn"); return; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - Log.e("SUTAgentWatcher", "SDcard mounted read only not starting agent now, try again in 60s"); + Log.e(LOGTAG, "SDcard mounted read only not starting agent now, try again in 60s"); return; } } @@ -979,7 +1007,7 @@ public class WatcherService extends Service boolean isProc = GetProcessInfo(sProgramName); if (bStartSUTAgent && !isProc) { - Log.i("SUTAgentWatcher", "Starting SUTAgent from watcher code"); + Log.i(LOGTAG, "Starting SUTAgent from watcher code"); Intent agentIntent = new Intent(); agentIntent.setPackage(sProgramName); agentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -1013,14 +1041,4 @@ public class WatcherService extends Service } } } - - private void SendNotification(String tickerText, String expandedText) { - Log.i("Watcher", expandedText); - } - - private void CancelNotification() { - NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(NOTIFICATION_ID); - } - } diff --git a/build/unix/mozconfig.linux32 b/build/unix/mozconfig.linux32 index 39bf637d8dc0..3b8c1e455e14 100644 --- a/build/unix/mozconfig.linux32 +++ b/build/unix/mozconfig.linux32 @@ -1,8 +1,9 @@ . "$topsrcdir/build/unix/mozconfig.linux" if test `uname -m` = "x86_64"; then - CC="$CC -m32" - CXX="$CXX -m32" + # -march=pentiumpro is what our 32-bit native toolchain defaults to + CC="$CC -m32 -march=pentiumpro" + CXX="$CXX -m32 -march=pentiumpro" ac_add_options --target=i686-pc-linux ac_add_options --x-libraries=/usr/lib export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index ec0b699eae32..beed3d51dfcd 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -1728,7 +1728,6 @@ public: #undef EVENT protected: - static void Trace(nsINode *tmp, const TraceCallbacks &cb, void *closure); static bool Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb); static void Unlink(nsINode *tmp); diff --git a/content/base/src/Attr.cpp b/content/base/src/Attr.cpp index 84b85e195dbd..7598af882c94 100644 --- a/content/base/src/Attr.cpp +++ b/content/base/src/Attr.cpp @@ -66,9 +66,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Attr) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttrMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Attr) - nsINode::Trace(tmp, aCallbacks, aClosure); -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Attr) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Attr) nsINode::Unlink(tmp); diff --git a/content/base/src/DirectionalityUtils.cpp b/content/base/src/DirectionalityUtils.cpp index cc10798fb470..d1ddff85325f 100644 --- a/content/base/src/DirectionalityUtils.cpp +++ b/content/base/src/DirectionalityUtils.cpp @@ -460,12 +460,12 @@ public: void RemoveEntry(nsINode* aTextNode, Element* aElement) { - if (mElements.Contains(aElement)) { - mElements.Remove(aElement); + NS_ASSERTION(mElements.Contains(aElement), + "element already removed from map"); - aElement->ClearHasDirAutoSet(); - aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy); - } + mElements.Remove(aElement); + aElement->ClearHasDirAutoSet(); + aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy); } private: diff --git a/content/base/src/FragmentOrElement.cpp b/content/base/src/FragmentOrElement.cpp index 0d44596c28a0..4a9279ee5917 100644 --- a/content/base/src/FragmentOrElement.cpp +++ b/content/base/src/FragmentOrElement.cpp @@ -1156,9 +1156,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement) } NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FragmentOrElement) - nsINode::Trace(tmp, aCallbacks, aClosure); -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement) void FragmentOrElement::MarkUserData(void* aObject, nsIAtom* aKey, void* aChild, diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 7f57587dc656..0eccf5208b19 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1803,7 +1803,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument) if (tmp->PreservingWrapper()) { NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando); } - nsINode::Trace(tmp, aCallbacks, aClosure); + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END @@ -2500,29 +2500,30 @@ nsDocument::InitCSP(nsIChannel* aChannel) } } - // ----- Figure out if we need to apply an app default CSP + // Figure out if we need to apply an app default CSP or a CSP from an app manifest bool applyAppDefaultCSP = false; + bool applyAppManifestCSP = false; + nsIPrincipal* principal = NodePrincipal(); - uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; + bool unknownAppId; + uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; + nsAutoString appManifestCSP; if (NS_SUCCEEDED(principal->GetUnknownAppId(&unknownAppId)) && !unknownAppId && NS_SUCCEEDED(principal->GetAppStatus(&appStatus))) { applyAppDefaultCSP = ( appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED || appStatus == nsIPrincipal::APP_STATUS_CERTIFIED); - // Bug 773981. Allow a per-app policy from the manifest. - // Just read the CSP from the manifest into cspHeaderValue. - // That way we don't have to change the rest of the function logic - if (applyAppDefaultCSP || appStatus == nsIPrincipal::APP_STATUS_INSTALLED) { - nsCOMPtr appsService = - do_GetService(APPS_SERVICE_CONTRACTID); - - if (appsService) { - uint32_t appId; - - if ( NS_SUCCEEDED(principal->GetAppId(&appId)) ) { - appsService->GetCSPByLocalId(appId, cspHeaderValue); + if (appStatus != nsIPrincipal::APP_STATUS_NOT_INSTALLED) { + nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); + if (appsService) { + uint32_t appId = 0; + if (NS_SUCCEEDED(principal->GetAppId(&appId))) { + appsService->GetCSPByLocalId(appId, appManifestCSP); + if (!appManifestCSP.IsEmpty()) { + applyAppManifestCSP = true; + } } } } @@ -2534,6 +2535,7 @@ nsDocument::InitCSP(nsIChannel* aChannel) // If there's no CSP to apply, go ahead and return early if (!applyAppDefaultCSP && + !applyAppManifestCSP && cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty() && cspOldHeaderValue.IsEmpty() && @@ -2572,7 +2574,13 @@ nsDocument::InitCSP(nsIChannel* aChannel) // Store the request context for violation reports csp->ScanRequestData(httpChannel); - // ----- process the app default policy, if necessary + // The CSP is refined in the following order: + // 1. Default app CSP, if applicable + // 2. App manifest CSP, if provided + // 3. HTTP header CSP, if provided + // Note that since each application of refinePolicy is a set intersection, + // the order in which multiple CSP's are refined does not matter. + if (applyAppDefaultCSP) { nsAdoptingString appCSP; if (appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED) { @@ -2588,6 +2596,11 @@ nsDocument::InitCSP(nsIChannel* aChannel) csp->RefinePolicy(appCSP, chanURI, specCompliantEnabled); } + if (applyAppManifestCSP) { + // Use the 1.0 CSP parser for apps if the pref to do so is set. + csp->RefinePolicy(appManifestCSP, chanURI, specCompliantEnabled); + } + // While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers // take priority. If any spec-compliant headers are present, the x- headers // are ignored, and the spec compliant parser is used. @@ -6854,7 +6867,7 @@ nsDocument::GetViewportInfo(uint32_t aDisplayWidth, mWidthStrEmpty = widthStr.IsEmpty(); mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode); mValidMaxScale = !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode); - + mViewportType = Specified; } case Specified: diff --git a/content/base/src/nsGenericDOMDataNode.cpp b/content/base/src/nsGenericDOMDataNode.cpp index ccfb78081e0c..2efc6cd62a0d 100644 --- a/content/base/src/nsGenericDOMDataNode.cpp +++ b/content/base/src/nsGenericDOMDataNode.cpp @@ -62,9 +62,7 @@ nsGenericDOMDataNode::~nsGenericDOMDataNode() } } -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGenericDOMDataNode) - nsINode::Trace(tmp, aCallbacks, aClosure); -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsGenericDOMDataNode) NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGenericDOMDataNode) return Element::CanSkip(tmp, aRemovingAllowed); diff --git a/content/base/src/nsINode.cpp b/content/base/src/nsINode.cpp index 845e01165eca..d696a8159aae 100644 --- a/content/base/src/nsINode.cpp +++ b/content/base/src/nsINode.cpp @@ -1163,14 +1163,6 @@ nsINode::GetContextForEventHandlers(nsresult* aRv) return nsContentUtils::GetContextForEventHandlers(this, aRv); } -/* static */ -void -nsINode::Trace(nsINode *tmp, const TraceCallbacks& cb, void *closure) -{ - tmp->TraceWrapper(cb, closure); -} - - bool nsINode::UnoptimizableCCNode() const { diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in index 8bb5f9baecef..abfeb27a04fe 100644 --- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -629,6 +629,7 @@ MOCHITEST_FILES_C= \ test_bug868999.html \ test_bug869000.html \ test_bug869002.html \ + test_bug876282.html \ $(NULL) # OOP tests don't work on Windows (bug 763081) or native-fennec diff --git a/content/base/test/test_bug876282.html b/content/base/test/test_bug876282.html new file mode 100644 index 000000000000..bb5a6a02a229 --- /dev/null +++ b/content/base/test/test_bug876282.html @@ -0,0 +1,45 @@ + + + + + Test for Bug 647518 + + + + +Mozilla Bug 647518 +

+ +
+
+
+ + diff --git a/content/html/content/public/HTMLMediaElement.h b/content/html/content/public/HTMLMediaElement.h index 889a77859e68..a8dcfbc59244 100644 --- a/content/html/content/public/HTMLMediaElement.h +++ b/content/html/content/public/HTMLMediaElement.h @@ -43,6 +43,8 @@ class MediaResource; class MediaDecoder; } +class nsITimer; + namespace mozilla { namespace dom { @@ -533,6 +535,8 @@ protected: WakeLockBoolWrapper(bool val = false) : mValue(val), mCanPlay(true), mOuter(nullptr) {} + ~WakeLockBoolWrapper(); + void SetOuter(HTMLMediaElement* outer) { mOuter = outer; } void SetCanPlay(bool aCanPlay); @@ -542,12 +546,15 @@ protected: bool operator !() const { return !mValue; } + static void TimerCallback(nsITimer* aTimer, void* aClosure); + private: void UpdateWakeLock(); bool mValue; bool mCanPlay; HTMLMediaElement* mOuter; + nsCOMPtr mTimer; }; /** diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp index eb6d852b8774..8c41ab4036d1 100644 --- a/content/html/content/src/HTMLMediaElement.cpp +++ b/content/html/content/src/HTMLMediaElement.cpp @@ -1987,6 +1987,8 @@ HTMLMediaElement::~HTMLMediaElement() if (mAudioStream) { mAudioStream->Shutdown(); } + + WakeLockRelease(); } void @@ -2106,7 +2108,8 @@ NS_IMETHODIMP HTMLMediaElement::Play() } HTMLMediaElement::WakeLockBoolWrapper& -HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val) { +HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val) +{ if (mValue == val) { return *this; } @@ -2116,6 +2119,13 @@ HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val) { return *this; } +HTMLMediaElement::WakeLockBoolWrapper::~WakeLockBoolWrapper() +{ + if (mTimer) { + mTimer->Cancel(); + } +} + void HTMLMediaElement::WakeLockBoolWrapper::SetCanPlay(bool aCanPlay) { @@ -2133,12 +2143,30 @@ HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock() bool playing = (!mValue && mCanPlay); if (playing) { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } mOuter->WakeLockCreate(); - } else { - mOuter->WakeLockRelease(); + } else if (!mTimer) { + // Don't release the wake lock immediately; instead, release it after a + // grace period. + int timeout = Preferences::GetInt("media.wakelock_timeout", 2000); + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + mTimer->InitWithFuncCallback(TimerCallback, this, timeout, + nsITimer::TYPE_ONE_SHOT); } } +void +HTMLMediaElement::WakeLockBoolWrapper::TimerCallback(nsITimer* aTimer, + void* aClosure) +{ + WakeLockBoolWrapper* wakeLock = static_cast(aClosure); + wakeLock->mOuter->WakeLockRelease(); + wakeLock->mTimer = nullptr; +} + void HTMLMediaElement::WakeLockCreate() { diff --git a/content/html/content/test/forms/test_input_sanitization.html b/content/html/content/test/forms/test_input_sanitization.html index c18216981e8b..9ec8ddcc5cde 100644 --- a/content/html/content/test/forms/test_input_sanitization.html +++ b/content/html/content/test/forms/test_input_sanitization.html @@ -36,6 +36,36 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=549475 * The value sanitization algorithm is just an internal spec concept after all. */ +// We buffer up the results of sets of sub-tests, and avoid outputting log +// entries for them all if they all pass. Otherwise, we have an enormous amount +// of test output. + +var delayedTests = []; +var anyFailedDelayedTests = false; + +function delayed_is(actual, expected, description) +{ + var result = actual == expected; + delayedTests.push({ actual: actual, expected: expected, description: description }); + if (!result) { + anyFailedDelayedTests = true; + } +} + +function flushDelayedTests(description) +{ + if (anyFailedDelayedTests) { + info("Outputting individual results for \"" + description + "\" due to failures in subtests"); + for (var test of delayedTests) { + is(test.actual, test.expected, test.description); + } + } else { + ok(true, description + " (" + delayedTests.length + " subtests)"); + } + delayedTests = []; + anyFailedDelayedTests = false; +} + // We are excluding "file" because it's too different from the other types. // And it has no sanitizing algorithm. var inputTypes = @@ -157,7 +187,7 @@ function sanitizeValue(aType, aValue) } } -function checkSanitizing(element) +function checkSanitizing(element, inputTypeDescription) { var testData = [ @@ -302,17 +332,17 @@ function checkSanitizing(element) for (value of testData) { element.setAttribute('value', value); - is(element.value, sanitizeValue(type, value), + delayed_is(element.value, sanitizeValue(type, value), "The value has not been correctly sanitized for type=" + type); - is(element.getAttribute('value'), value, + delayed_is(element.getAttribute('value'), value, "The content value should not have been sanitized"); if (type in valueModeValue) { element.setAttribute('value', 'tulip'); element.value = value; - is(element.value, sanitizeValue(type, value), + delayed_is(element.value, sanitizeValue(type, value), "The value has not been correctly sanitized for type=" + type); - is(element.getAttribute('value'), 'tulip', + delayed_is(element.getAttribute('value'), 'tulip', "The content value should not have been sanitized"); } @@ -320,26 +350,28 @@ function checkSanitizing(element) form.reset(); element.type = 'checkbox'; // We know this type has no sanitizing algorithm. element.setAttribute('value', value); - is(element.value, value, "The value should not have been sanitized"); + delayed_is(element.value, value, "The value should not have been sanitized"); element.type = type; - is(element.value, sanitizeValue(type, value), + delayed_is(element.value, sanitizeValue(type, value), "The value has not been correctly sanitized for type=" + type); - is(element.getAttribute('value'), value, + delayed_is(element.getAttribute('value'), value, "The content value should not have been sanitized"); element.setAttribute('value', ''); form.reset(); element.setAttribute('value', value); form.reset(); - is(element.value, sanitizeValue(type, value), + delayed_is(element.value, sanitizeValue(type, value), "The value has not been correctly sanitized for type=" + type); - is(element.getAttribute('value'), value, + delayed_is(element.getAttribute('value'), value, "The content value should not have been sanitized"); // Cleaning-up. element.setAttribute('value', ''); form.reset(); } + + flushDelayedTests(inputTypeDescription); } for (type of inputTypes) { @@ -349,17 +381,17 @@ for (type of inputTypes) { element.type = type; form.appendChild(element); - checkSanitizing(element); // no frame, no editor + checkSanitizing(element, "type=" + type + ", no frame, no editor"); element.style.display = ""; - checkSanitizing(element); // frame, no editor + checkSanitizing(element, "type=" + type + ", frame, no editor"); element.focus(); element.blur(); - checkSanitizing(element); // frame, editor + checkSanitizing(element, "type=" + type + ", frame, editor"); element.style.display = "none"; - checkSanitizing(element); // no frame, editor + checkSanitizing(element, "type=" + type + ", no frame, editor"); form.removeChild(element); } diff --git a/content/html/content/test/test_audio_wakelock.html b/content/html/content/test/test_audio_wakelock.html index 853d08209d5f..81472ef9b00c 100644 --- a/content/html/content/test/test_audio_wakelock.html +++ b/content/html/content/test/test_audio_wakelock.html @@ -20,8 +20,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=868943 /** Test for Bug 868943 **/ -SpecialPowers.addPermission("power", true, document); - function testAudioPlayPause() { var lockState = true; var count = 0; @@ -32,9 +30,11 @@ function testAudioPlayPause() { audio.src = "wakelock.ogg"; content.appendChild(audio); + var startDate; audio.addEventListener('progress', function() { lockState = false; audio.pause(); + startDate = new Date(); }); navigator.mozPower.addWakeLockListener(function testAudioPlayListener(topic, state) { @@ -49,6 +49,9 @@ function testAudioPlayPause() { // count == 2 is when the cpu wakelock is released if (count == 2) { + var diffDate = (new Date() - startDate); + ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release"); + content.removeChild(audio); navigator.mozPower.removeWakeLockListener(testAudioPlayListener); runTests(); @@ -68,6 +71,11 @@ function testAudioPlay() { audio.src = "wakelock.ogg"; content.appendChild(audio); + var startDate; + audio.addEventListener('progress', function() { + startDate = new Date(); + }); + navigator.mozPower.addWakeLockListener(function testAudioPlayListener(topic, state) { is(topic, "cpu", "Audio element locked the target == cpu"); var locked = state == "locked-foreground" || @@ -84,6 +92,9 @@ function testAudioPlay() { // The next step is to unlock the resource. lockState = false; } else if (count == 2) { + var diffDate = (new Date() - startDate); + ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release"); + content.removeChild(audio); navigator.mozPower.removeWakeLockListener(testAudioPlayListener); runTests(); @@ -104,8 +115,10 @@ function runTests() { test(); }; +SpecialPowers.addPermission("power", true, document); +SpecialPowers.pushPrefEnv({"set": [["media.wakelock_timeout", 500]]}, runTests); + SimpleTest.waitForExplicitFinish(); -runTests(); diff --git a/content/html/content/test/test_video_wakelock.html b/content/html/content/test/test_video_wakelock.html index f4c03b95e00e..a3132a79083c 100644 --- a/content/html/content/test/test_video_wakelock.html +++ b/content/html/content/test/test_video_wakelock.html @@ -20,8 +20,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=868943 /** Test for Bug 868943 **/ -SpecialPowers.addPermission("power", true, document); - function testVideoPlayPause() { var lockState = true; var count = 0; @@ -32,6 +30,15 @@ function testVideoPlayPause() { video.src = "wakelock.ogv"; content.appendChild(video); + var startDate; + video.addEventListener('progress', function() { + startDate = new Date(); + + // The next step is to unlock the resource. + lockState = false; + video.pause(); + }); + navigator.mozPower.addWakeLockListener(function testVideoPlayPauseListener(topic, state) { is(topic, "screen", "Video element locked the target == screen"); var locked = state == "locked-foreground" || @@ -40,11 +47,10 @@ function testVideoPlayPause() { is(locked, lockState, "Video element locked the screen - paused"); count++; - if (count == 1) { - // The next step is to unlock the resource. - lockState = false; - video.pause(); - } else if (count == 2) { + if (count == 2) { + var diffDate = (new Date() - startDate); + ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release"); + content.removeChild(video); navigator.mozPower.removeWakeLockListener(testVideoPlayPauseListener); runTests(); @@ -64,6 +70,11 @@ function testVideoPlay() { video.src = "wakelock.ogv"; content.appendChild(video); + var startDate; + video.addEventListener('progress', function() { + startDate = new Date(); + }); + navigator.mozPower.addWakeLockListener(function testVideoPlayListener(topic, state) { is(topic, "screen", "Video element locked the target == screen"); var locked = state == "locked-foreground" || @@ -76,6 +87,9 @@ function testVideoPlay() { // The next step is to unlock the resource. lockState = false; } else if (count == 2) { + var diffDate = (new Date() - startDate); + ok(diffDate > 200, "There was at least milliseconds between the stop and the wakelock release"); + content.removeChild(video); navigator.mozPower.removeWakeLockListener(testVideoPlayListener); runTests(); @@ -96,8 +110,10 @@ function runTests() { test(); }; +SpecialPowers.addPermission("power", true, document); +SpecialPowers.pushPrefEnv({"set": [["media.wakelock_timeout", 500]]}, runTests); + SimpleTest.waitForExplicitFinish(); -runTests(); diff --git a/content/media/test/crashtests/876249.html b/content/media/test/crashtests/876249.html new file mode 100644 index 000000000000..6f72b40bc1d0 --- /dev/null +++ b/content/media/test/crashtests/876249.html @@ -0,0 +1,27 @@ + diff --git a/content/media/test/crashtests/876834.html b/content/media/test/crashtests/876834.html new file mode 100644 index 000000000000..cf8d35b7ad3d --- /dev/null +++ b/content/media/test/crashtests/876834.html @@ -0,0 +1,4 @@ + + diff --git a/content/media/test/crashtests/crashtests.list b/content/media/test/crashtests/crashtests.list index dec0d06dc8bd..03830dfea2f2 100644 --- a/content/media/test/crashtests/crashtests.list +++ b/content/media/test/crashtests/crashtests.list @@ -22,4 +22,6 @@ load 874952.html load 875144.html load 875596.html load 875911.html +load 876249.html load 876252.html +load 876834.html diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index 474ff4d83ad5..c4d8b1316486 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -25,6 +25,7 @@ #include "ChannelMergerNode.h" #include "ChannelSplitterNode.h" #include "WaveShaperNode.h" +#include "WaveTable.h" #include "nsNetUtil.h" // Note that this number is an arbitrary large value to protect against OOM @@ -108,7 +109,7 @@ AudioContext::Constructor(const GlobalObject& aGlobal, return nullptr; } - if (aSampleRate <= 0.0f) { + if (aSampleRate <= 0.0f || aSampleRate >= TRACK_RATE_MAX) { // The DOM binding protects us against infinity and NaN aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; @@ -308,6 +309,24 @@ AudioContext::CreateBiquadFilter() return filterNode.forget(); } +already_AddRefed +AudioContext::CreateWaveTable(const Float32Array& aRealData, + const Float32Array& aImagData, + ErrorResult& aRv) +{ + if (aRealData.Length() != aImagData.Length() || + aRealData.Length() == 0 || + aRealData.Length() > 4096) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr waveTable = + new WaveTable(this, aRealData.Data(), aRealData.Length(), + aImagData.Data(), aImagData.Length()); + return waveTable.forget(); +} + AudioListener* AudioContext::Listener() { diff --git a/content/media/webaudio/AudioContext.h b/content/media/webaudio/AudioContext.h index 1bd9a821c8ba..7369ddd1afe1 100644 --- a/content/media/webaudio/AudioContext.h +++ b/content/media/webaudio/AudioContext.h @@ -54,6 +54,7 @@ class OfflineRenderSuccessCallback; class PannerNode; class ScriptProcessorNode; class WaveShaperNode; +class WaveTable; class AudioContext MOZ_FINAL : public nsDOMEventTargetHelper, public EnableWebAudioCheck @@ -178,6 +179,10 @@ public: already_AddRefed CreateBiquadFilter(); + already_AddRefed + CreateWaveTable(const Float32Array& aRealData, const Float32Array& aImagData, + ErrorResult& aRv); + void DecodeAudioData(const ArrayBuffer& aBuffer, DecodeSuccessCallback& aSuccessCallback, const Optional >& aFailureCallback); diff --git a/content/media/webaudio/WaveTable.cpp b/content/media/webaudio/WaveTable.cpp new file mode 100644 index 000000000000..ab3c37508c1e --- /dev/null +++ b/content/media/webaudio/WaveTable.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include "WaveTable.h" +#include "AudioContext.h" +#include "mozilla/dom/WaveTableBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(WaveTable, mContext) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WaveTable, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WaveTable, Release) + +WaveTable::WaveTable(AudioContext* aContext, + const float* aRealData, + uint32_t aRealDataLength, + const float* aImagData, + uint32_t aImagDataLength) + : mContext(aContext) +{ + MOZ_ASSERT(aContext); + SetIsDOMBinding(); +} + +JSObject* +WaveTable::WrapObject(JSContext* aCx, JS::Handle aScope) +{ + return WaveTableBinding::Wrap(aCx, aScope, this); +} + +} +} + diff --git a/content/media/webaudio/WaveTable.h b/content/media/webaudio/WaveTable.h new file mode 100644 index 000000000000..e60c8ff1a18a --- /dev/null +++ b/content/media/webaudio/WaveTable.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef WaveTable_h_ +#define WaveTable_h_ + +#include "nsWrapperCache.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" +#include "EnableWebAudioCheck.h" +#include "AudioContext.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +namespace dom { + +class WaveTable MOZ_FINAL : public nsWrapperCache, + public EnableWebAudioCheck +{ +public: + WaveTable(AudioContext* aContext, + const float* aRealData, + uint32_t aRealDataLength, + const float* aImagData, + uint32_t aImagDataLength); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WaveTable) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WaveTable) + + AudioContext* GetParentObject() const + { + return mContext; + } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aScope) MOZ_OVERRIDE; + +private: + nsRefPtr mContext; +}; + +} +} + +#endif + diff --git a/content/media/webaudio/moz.build b/content/media/webaudio/moz.build index c5dd3664e1db..d8b2c50bd78a 100644 --- a/content/media/webaudio/moz.build +++ b/content/media/webaudio/moz.build @@ -38,6 +38,7 @@ EXPORTS.mozilla.dom += [ 'PannerNode.h', 'ScriptProcessorNode.h', 'WaveShaperNode.h', + 'WaveTable.h', ] CPP_SOURCES += [ @@ -63,6 +64,7 @@ CPP_SOURCES += [ 'ScriptProcessorNode.cpp', 'ThreeDPoint.cpp', 'WaveShaperNode.cpp', + 'WaveTable.cpp', 'WebAudioUtils.cpp', ] diff --git a/content/media/webaudio/test/Makefile.in b/content/media/webaudio/test/Makefile.in index 79369349920d..55dcb32549c1 100644 --- a/content/media/webaudio/test/Makefile.in +++ b/content/media/webaudio/test/Makefile.in @@ -69,6 +69,7 @@ MOCHITEST_FILES := \ test_waveShaper.html \ test_waveShaperNoCurve.html \ test_waveShaperZeroLengthCurve.html \ + test_waveTable.html \ ting.ogg \ ting-expected.wav \ ting-dualchannel44.1.ogg \ diff --git a/content/media/webaudio/test/test_waveTable.html b/content/media/webaudio/test/test_waveTable.html new file mode 100644 index 000000000000..579a42b5975c --- /dev/null +++ b/content/media/webaudio/test/test_waveTable.html @@ -0,0 +1,36 @@ + + + + Test the WaveTable interface + + + + + +
+
+
+ + diff --git a/dom/alarm/AlarmService.jsm b/dom/alarm/AlarmService.jsm index ee3f29619a6b..556170ff9073 100644 --- a/dom/alarm/AlarmService.jsm +++ b/dom/alarm/AlarmService.jsm @@ -20,6 +20,10 @@ Cu.import("resource://gre/modules/AlarmDB.jsm"); this.EXPORTED_SYMBOLS = ["AlarmService"]; +XPCOMUtils.defineLazyGetter(this, "appsService", function() { + return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService); +}); + XPCOMUtils.defineLazyServiceGetter(this, "ppmm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageListenerManager"); @@ -49,6 +53,7 @@ this.AlarmService = { init: function init() { debug("init()"); Services.obs.addObserver(this, "profile-change-teardown", false); + Services.obs.addObserver(this, "webapps-clear-data",false); this._currentTimezoneOffset = (new Date()).getTimezoneOffset(); @@ -496,12 +501,30 @@ this.AlarmService = { switch (aTopic) { case "profile-change-teardown": this.uninit(); + break; + case "webapps-clear-data": + let params = + aSubject.QueryInterface(Ci.mozIApplicationClearPrivateDataParams); + let manifestURL = appsService.getManifestURLByLocalId(params.appId); + this._db.getAll( + manifestURL, + function getAllSuccessCb(aAlarms) { + aAlarms.forEach(function removeAlarm(aAlarm) { + this.remove(aAlarm.id, manifestURL); + }, this); + }.bind(this), + function getAllErrorCb(aErrorMsg) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + } + ); + break; } }, uninit: function uninit() { debug("uninit()"); Services.obs.removeObserver(this, "profile-change-teardown"); + Services.obs.removeObserver(this, "webapps-clear-data"); this._messages.forEach(function(aMsgName) { ppmm.removeMessageListener(aMsgName, this); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index a490b44c6e37..0d796daf3f8d 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -4429,13 +4429,19 @@ nsGlobalWindow::RequestAnimationFrame(const JS::Value& aCallback, NS_IMETHODIMP nsGlobalWindow::MozCancelRequestAnimationFrame(int32_t aHandle) { - return MozCancelAnimationFrame(aHandle); + return CancelAnimationFrame(aHandle); } NS_IMETHODIMP nsGlobalWindow::MozCancelAnimationFrame(int32_t aHandle) { - FORWARD_TO_INNER(MozCancelAnimationFrame, (aHandle), + return CancelAnimationFrame(aHandle); +} + +NS_IMETHODIMP +nsGlobalWindow::CancelAnimationFrame(int32_t aHandle) +{ + FORWARD_TO_INNER(CancelAnimationFrame, (aHandle), NS_ERROR_NOT_INITIALIZED); if (!mDoc) { diff --git a/dom/bindings/BindingGen.py b/dom/bindings/BindingGen.py index 312b8a171ec1..54523af53e02 100644 --- a/dom/bindings/BindingGen.py +++ b/dom/bindings/BindingGen.py @@ -52,7 +52,20 @@ def main(): if all(f.endswith("Binding") or f == "ParserResults.pkl" for f in changedDeps): toRegenerate = filter(lambda f: f.endswith("Binding"), changedDeps) - toRegenerate = map(lambda f: f[:-len("Binding")] + ".webidl", toRegenerate) + if len(toRegenerate) == 0 and len(changedDeps) == 1: + # Work around build system bug 874923: if we get here that means + # that changedDeps contained only one entry and it was + # "ParserResults.pkl". That should never happen: if the + # ParserResults.pkl changes then either one of the globalgen files + # changed (in which case we wouldn't be in this "only + # ParserResults.pkl and *Binding changed" code) or some .webidl + # files changed (and then the corresponding *Binding files should + # show up in changedDeps). Since clearly the build system is + # confused, just regenerate everything to be safe. + toRegenerate = allWebIDLFiles + else: + toRegenerate = map(lambda f: f[:-len("Binding")] + ".webidl", + toRegenerate) else: toRegenerate = allWebIDLFiles diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index e35ac1af11a1..89aae18b085e 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1091,6 +1091,10 @@ DOMInterfaces = { 'VideoStreamTrack': { }, +'WaveTable' : { + 'nativeOwnership': 'refcounted' +}, + 'WebGLActiveInfo': { 'nativeType': 'mozilla::WebGLActiveInfo', 'headerFile': 'WebGLContext.h', diff --git a/dom/indexedDB/TransactionThreadPool.cpp b/dom/indexedDB/TransactionThreadPool.cpp index 6c96ea6bcc5b..a14923c91f22 100644 --- a/dom/indexedDB/TransactionThreadPool.cpp +++ b/dom/indexedDB/TransactionThreadPool.cpp @@ -672,7 +672,8 @@ NS_IMETHODIMP TransactionThreadPoolListener::OnThreadCreated() { MOZ_ASSERT(!NS_IsMainThread()); - profiler_register_thread("IndexedDB Transaction"); + char aLocal; + profiler_register_thread("IndexedDB Transaction", &aLocal); return NS_OK; } diff --git a/dom/interfaces/base/nsIDOMWindow.idl b/dom/interfaces/base/nsIDOMWindow.idl index a5aff8b949fd..4f2f67c095e9 100644 --- a/dom/interfaces/base/nsIDOMWindow.idl +++ b/dom/interfaces/base/nsIDOMWindow.idl @@ -25,7 +25,7 @@ interface nsIVariant; * @see */ -[scriptable, uuid(e0f33b20-72ef-415b-9ff2-8bf176f581f8)] +[scriptable, uuid(be62660a-e3f6-409c-a4a9-378364a9526f)] interface nsIDOMWindow : nsISupports { // the current browsing context @@ -454,6 +454,7 @@ interface nsIDOMWindow : nsISupports void mozCancelAnimationFrame(in long aHandle); // Backwards-compat shim for now to make Google maps work void mozCancelRequestAnimationFrame(in long aHandle); + void cancelAnimationFrame(in long aHandle); /** * The current animation start time in milliseconds since the epoch. @@ -508,5 +509,5 @@ interface nsIDOMWindowPerformance : nsISupports * Empty interface for compatibility with older versions. * @deprecated Use nsIDOMWindow instead */ -[scriptable, uuid(36aeaa8e-3126-49de-9581-276dd7117826)] +[scriptable, uuid(ad5768c7-8668-4cd4-bcac-3d0a400d50be)] interface nsIDOMWindowInternal : nsIDOMWindow {}; diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index e0d7bffed0c9..6a6b3b674c5f 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -2261,16 +2261,14 @@ _getvalue(NPP npp, NPNVariable variable, void *result) } case kJavaContext_ANPGetValue: { - LOG("get context"); - JNIEnv* env = GetJNIForThread(); - if (!env) + AndroidBridge *bridge = AndroidBridge::Bridge(); + if (!bridge) + return NPERR_GENERIC_ERROR; + + jobject ret = bridge->GetContext(); + if (!ret) return NPERR_GENERIC_ERROR; - jclass cls = env->FindClass("org/mozilla/gecko/GeckoApp"); - jfieldID field = env->GetStaticFieldID(cls, "mAppContext", - "Lorg/mozilla/gecko/GeckoApp;"); - jobject ret = env->GetStaticObjectField(cls, field); - env->DeleteLocalRef(cls); int32_t* i = reinterpret_cast(result); *i = reinterpret_cast(ret); return NPERR_NO_ERROR; diff --git a/dom/plugins/base/nsPluginsDirWin.cpp b/dom/plugins/base/nsPluginsDirWin.cpp index cf3eaa1d7bfd..114ef7e05e88 100644 --- a/dom/plugins/base/nsPluginsDirWin.cpp +++ b/dom/plugins/base/nsPluginsDirWin.cpp @@ -27,6 +27,41 @@ #include "nsIFile.h" #include "nsUnicharUtils.h" +#include +#define SHOCKWAVE_BASE_FILENAME L"np32dsw" +/** + * Determines whether or not SetDllDirectory should be called for this plugin. + * + * @param pluginFilePath The full path of the plugin file + * @return true if SetDllDirectory can be called for the plugin + */ +bool +ShouldProtectPluginCurrentDirectory(LPCWSTR pluginFilePath) +{ + LPCWSTR passedInFilename = PathFindFileName(pluginFilePath); + if (!passedInFilename) { + return true; + } + + // Somewhere in the middle of 11.6 version of Shockwave, naming of the DLL + // after its version number is introduced. + if (!wcsicmp(passedInFilename, SHOCKWAVE_BASE_FILENAME L".dll")) { + return false; + } + + // Shockwave versions before 1202122 will break if you call SetDllDirectory + const uint64_t kFixedShockwaveVersion = 1202122; + uint64_t version; + int found = swscanf(passedInFilename, SHOCKWAVE_BASE_FILENAME L"_%llu.dll", + &version); + if (found && version < kFixedShockwaveVersion) { + return false; + } + + // We always want to call SetDllDirectory otherwise + return true; +} + using namespace mozilla; /* Local helper functions */ @@ -245,17 +280,13 @@ nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) bool protectCurrentDirectory = true; - nsAutoString pluginFolderPath; - mPlugin->GetPath(pluginFolderPath); - - int32_t idx = pluginFolderPath.RFindChar('\\'); - if (kNotFound == idx) - return NS_ERROR_FILE_INVALID_PATH; - - if (Substring(pluginFolderPath, idx).LowerCaseEqualsLiteral("\\np32dsw.dll")) { - protectCurrentDirectory = false; - } + nsAutoString pluginFilePath; + mPlugin->GetPath(pluginFilePath); + protectCurrentDirectory = + ShouldProtectPluginCurrentDirectory(pluginFilePath.BeginReading()); + nsAutoString pluginFolderPath = pluginFilePath; + int32_t idx = pluginFilePath.RFindChar('\\'); pluginFolderPath.SetLength(idx); BOOL restoreOrigDir = FALSE; diff --git a/dom/plugins/ipc/PluginProcessChild.cpp b/dom/plugins/ipc/PluginProcessChild.cpp index 9649421a73be..0b64ad9d50fb 100644 --- a/dom/plugins/ipc/PluginProcessChild.cpp +++ b/dom/plugins/ipc/PluginProcessChild.cpp @@ -15,6 +15,7 @@ #ifdef XP_WIN #include +bool ShouldProtectPluginCurrentDirectory(LPCWSTR pluginFilePath); #endif using mozilla::ipc::IOThreadChild; @@ -37,6 +38,7 @@ std::size_t caseInsensitiveFind(std::string aHaystack, std::string aNeedle) { namespace mozilla { namespace plugins { + bool PluginProcessChild::Init() { @@ -102,21 +104,12 @@ PluginProcessChild::Init() CommandLine::ForCurrentProcess()->GetLooseValues(); NS_ABORT_IF_FALSE(values.size() >= 1, "not enough loose args"); - pluginFilename = WideToUTF8(values[0]); - - bool protectCurrentDirectory = true; - // Don't use SetDllDirectory for Shockwave Director - const std::string shockwaveDirectorPluginFilename("\\np32dsw.dll"); - std::size_t index = caseInsensitiveFind(pluginFilename, shockwaveDirectorPluginFilename); - if (index != std::string::npos && - index + shockwaveDirectorPluginFilename.length() == pluginFilename.length()) { - protectCurrentDirectory = false; - } - if (protectCurrentDirectory) { + if (ShouldProtectPluginCurrentDirectory(values[0].c_str())) { SanitizeEnvironmentVariables(); SetDllDirectory(L""); } + pluginFilename = WideToUTF8(values[0]); #else # error Sorry #endif diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index 589d781c5e92..66ca0f4575df 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -874,7 +874,7 @@ NetworkManager.prototype = { try { let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" + this.possibleInterface[i]); - if (file.IsDirectory()) { + if (file.exists()) { return this.possibleInterface[i]; } } catch (e) { diff --git a/dom/webidl/AudioContext.webidl b/dom/webidl/AudioContext.webidl index f02d958e2bd1..7d45abfe34cf 100644 --- a/dom/webidl/AudioContext.webidl +++ b/dom/webidl/AudioContext.webidl @@ -61,6 +61,9 @@ interface AudioContext : EventTarget { [Creator] DynamicsCompressorNode createDynamicsCompressor(); + [Creator, Throws] + WaveTable createWaveTable(Float32Array real, Float32Array imag); + }; /* diff --git a/dom/webidl/WaveTable.webidl b/dom/webidl/WaveTable.webidl new file mode 100644 index 000000000000..5a048742cfc4 --- /dev/null +++ b/dom/webidl/WaveTable.webidl @@ -0,0 +1,17 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +[PrefControlled] +interface WaveTable { + +}; + diff --git a/dom/webidl/WebIDL.mk b/dom/webidl/WebIDL.mk index a0dd51277bb8..fb11212cdf6c 100644 --- a/dom/webidl/WebIDL.mk +++ b/dom/webidl/WebIDL.mk @@ -338,6 +338,7 @@ webidl_files = \ USSDReceivedEvent.webidl \ VideoStreamTrack.webidl \ WaveShaperNode.webidl \ + WaveTable.webidl \ Window.webidl \ XMLDocument.webidl \ XMLHttpRequest.webidl \ diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index e7fbc21098c3..d9ffca31cf05 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -514,7 +514,8 @@ public: JSRuntime* rt = JS_GetRuntime(cx); - profiler_register_thread("WebWorker"); + char aLocal; + profiler_register_thread("WebWorker", &aLocal); #ifdef MOZ_ENABLE_PROFILER_SPS if (PseudoStack* stack = mozilla_get_pseudo_stack()) stack->sampleRuntime(rt); diff --git a/gfx/cairo/cairo/src/cairo-d2d-surface.cpp b/gfx/cairo/cairo/src/cairo-d2d-surface.cpp index b76f20a9ed70..c3addac0d9e6 100644 --- a/gfx/cairo/cairo/src/cairo-d2d-surface.cpp +++ b/gfx/cairo/cairo/src/cairo-d2d-surface.cpp @@ -3232,7 +3232,12 @@ _cairo_d2d_paint(void *surface, op = _cairo_d2d_simplify_operator(op, source); if (op == CAIRO_OPERATOR_SOURCE) { - return CAIRO_INT_STATUS_UNSUPPORTED; + if (!clip) { + _cairo_d2d_clear(d2dsurf, NULL); + op = CAIRO_OPERATOR_OVER; + } else { + return CAIRO_INT_STATUS_UNSUPPORTED; + } } if (op == CAIRO_OPERATOR_CLEAR) { diff --git a/gfx/graphite2/README.mozilla b/gfx/graphite2/README.mozilla index 4994b2c665fa..525ba23ab56b 100644 --- a/gfx/graphite2/README.mozilla +++ b/gfx/graphite2/README.mozilla @@ -1,6 +1,6 @@ This directory contains the Graphite2 library from http://hg.palaso.org/graphitedev -Current version derived from upstream changeset 51e72e74b9a6 +Current version derived from upstream changeset 09707dd22634 See gfx/graphite2/moz-gr-update.sh for update procedure. diff --git a/gfx/graphite2/include/graphite2/Font.h b/gfx/graphite2/include/graphite2/Font.h index 3afcace95208..4c9451eb2679 100644 --- a/gfx/graphite2/include/graphite2/Font.h +++ b/gfx/graphite2/include/graphite2/Font.h @@ -30,7 +30,7 @@ #define GR2_VERSION_MAJOR 1 #define GR2_VERSION_MINOR 2 -#define GR2_VERSION_BUGFIX 0 +#define GR2_VERSION_BUGFIX 2 #ifdef __cplusplus extern "C" diff --git a/gfx/graphite2/src/Bidi.cpp b/gfx/graphite2/src/Bidi.cpp index b612a81e6473..c97eeb1738df 100644 --- a/gfx/graphite2/src/Bidi.cpp +++ b/gfx/graphite2/src/Bidi.cpp @@ -37,10 +37,10 @@ enum DirCode { // Hungarian: dirc R = 2, // right-to-left, strong - R AL = 3, // Arabic letter, right-to-left, strong, AR EN = 4, // European number, left-to-right, weak - EN - ES = 5, // European separator, left-to-right, weak - ES + EUS = 5, // European separator, left-to-right, weak - ES ET = 6, // European number terminator, left-to-right, weak - ET AN = 7, // Arabic number, left-to-right, weak - AN - CS = 8, // Common number separator, left-to-right, weak - CS + CUS = 8, // Common number separator, left-to-right, weak - CS WS = 9, // white space, neutral - WS BN = 10, // boundary neutral - BN diff --git a/gfx/graphite2/src/CMakeLists.txt b/gfx/graphite2/src/CMakeLists.txt index ef0f8a7b6dd8..f76cbed114c4 100644 --- a/gfx/graphite2/src/CMakeLists.txt +++ b/gfx/graphite2/src/CMakeLists.txt @@ -52,6 +52,9 @@ if (GRAPHITE2_NTRACING) set(TRACING) endif (GRAPHITE2_NTRACING) +if (GRAPHITE2_TELEMETRY) + add_definitions(-DGRAPHITE2_TELEMETRY) +endif (GRAPHITE2_TELEMETRY) set(GRAPHITE_HEADERS ../include/graphite2/Font.h diff --git a/gfx/graphite2/src/CmapCache.cpp b/gfx/graphite2/src/CmapCache.cpp index c5e4be0366fb..f388df9d2898 100644 --- a/gfx/graphite2/src/CmapCache.cpp +++ b/gfx/graphite2/src/CmapCache.cpp @@ -37,6 +37,7 @@ using namespace graphite2; const void * bmp_subtable(const Face::Table & cmap) { const void * stbl; + if (!cmap.size()) return 0; if (TtfUtil::CheckCmapSubtable4(stbl = TtfUtil::FindCmapSubtable(cmap, 3, 1, cmap.size())) || TtfUtil::CheckCmapSubtable4(stbl = TtfUtil::FindCmapSubtable(cmap, 0, 3, cmap.size())) || TtfUtil::CheckCmapSubtable4(stbl = TtfUtil::FindCmapSubtable(cmap, 0, 2, cmap.size())) @@ -49,6 +50,7 @@ const void * bmp_subtable(const Face::Table & cmap) const void * smp_subtable(const Face::Table & cmap) { const void * stbl; + if (!cmap.size()) return 0; if (TtfUtil::CheckCmapSubtable12(stbl = TtfUtil::FindCmapSubtable(cmap, 3, 10, cmap.size())) || TtfUtil::CheckCmapSubtable12(stbl = TtfUtil::FindCmapSubtable(cmap, 0, 4, cmap.size()))) return stbl; @@ -109,6 +111,7 @@ CachedCmap::CachedCmap(const Face & face) CachedCmap::~CachedCmap() throw() { + if (!m_blocks) return; unsigned int numBlocks = (m_isBmpOnly)? 0x100 : 0x1100; for (unsigned int i = 0; i < numBlocks; i++) free(m_blocks[i]); diff --git a/gfx/graphite2/src/Code.cpp b/gfx/graphite2/src/Code.cpp index 656582fcee1f..66f60cdd0025 100644 --- a/gfx/graphite2/src/Code.cpp +++ b/gfx/graphite2/src/Code.cpp @@ -144,6 +144,9 @@ Machine::Code::Code(bool is_constraint, const byte * bytecode_begin, const byte : _code(0), _data(0), _data_size(0), _instr_count(0), _max_ref(0), _status(loaded), _constraint(is_constraint), _modify(false), _delete(false), _own(true) { +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _code_cat(face.tele.code); +#endif assert(bytecode_begin != 0); if (bytecode_begin == bytecode_end) { @@ -210,12 +213,16 @@ Machine::Code::Code(bool is_constraint, const byte * bytecode_begin, const byte assert((bytecode_end - bytecode_begin) >= std::ptrdiff_t(_data_size)); _code = static_cast(realloc(_code, (_instr_count+1)*sizeof(instr))); _data = static_cast(realloc(_data, _data_size*sizeof(byte))); - - // Make this RET_ZERO, we should never reach this but just in case ... - _code[_instr_count] = op_to_fn[RET_ZERO].impl[_constraint]; if (!_code) failure(alloc_failed); + + // Make this RET_ZERO, we should never reach this but just in case ... + _code[_instr_count] = op_to_fn[RET_ZERO].impl[_constraint]; + +#ifdef GRAPHITE2_TELEMETRY + telemetry::count_bytes(_data_size + (_instr_count+1)*sizeof(instr)); +#endif } Machine::Code::~Code() throw () diff --git a/gfx/graphite2/src/Face.cpp b/gfx/graphite2/src/Face.cpp index 3428aeaccf4d..a8b8f0ce21a0 100644 --- a/gfx/graphite2/src/Face.cpp +++ b/gfx/graphite2/src/Face.cpp @@ -78,6 +78,9 @@ float Face::default_glyph_advance(const void* font_ptr, gr_uint16 glyphid) bool Face::readGlyphs(uint32 faceOptions) { +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _glyph_cat(tele.glyph); +#endif if (faceOptions & gr_face_cacheCmap) m_cmap = new CachedCmap(*this); else @@ -98,6 +101,9 @@ bool Face::readGlyphs(uint32 faceOptions) bool Face::readGraphite(const Table & silf) { +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _silf_cat(tele.silf); +#endif const byte * p = silf; if (!p) return false; @@ -145,7 +151,8 @@ bool Face::runGraphite(Segment *seg, const Silf *aSilf) const #endif bool res = aSilf->runGraphite(seg, 0, aSilf->justificationPass()); - res &= aSilf->runGraphite(seg, aSilf->positionPass(), aSilf->numPasses()); + if (res) + res = aSilf->runGraphite(seg, aSilf->positionPass(), aSilf->numPasses()); #if !defined GRAPHITE2_NTRACING if (dbgout) diff --git a/gfx/graphite2/src/FileFace.cpp b/gfx/graphite2/src/FileFace.cpp index dc2f017ed004..79a3213530eb 100644 --- a/gfx/graphite2/src/FileFace.cpp +++ b/gfx/graphite2/src/FileFace.cpp @@ -78,7 +78,7 @@ const void *FileFace::get_table_fn(const void* appFaceHandle, unsigned int name, if (appFaceHandle == 0) return 0; const FileFace & file_face = *static_cast(appFaceHandle); - char *tbl; + void *tbl; size_t tbl_offset, tbl_len; if (!TtfUtil::GetTableInfo(name, file_face._header_tbl, file_face._table_dir, tbl_offset, tbl_len)) return 0; @@ -87,7 +87,7 @@ const void *FileFace::get_table_fn(const void* appFaceHandle, unsigned int name, || fseek(file_face._file, tbl_offset, SEEK_SET) != 0) return 0; - tbl = gralloc(tbl_len); + tbl = malloc(tbl_len); if (fread(tbl, 1, tbl_len, file_face._file) != tbl_len) { free(tbl); diff --git a/gfx/graphite2/src/NameTable.cpp b/gfx/graphite2/src/NameTable.cpp index d29b92bc40e1..c9eb7651fe5b 100644 --- a/gfx/graphite2/src/NameTable.cpp +++ b/gfx/graphite2/src/NameTable.cpp @@ -37,7 +37,7 @@ NameTable::NameTable(const void* data, size_t length, uint16 platformId, uint16 m_platformOffset(0), m_platformLastRecord(0), m_nameDataLength(0), m_table(0), m_nameData(NULL) { - void *pdata = malloc(length); + void *pdata = gralloc(length); if (!pdata) return; memcpy(pdata, data, length); m_table = reinterpret_cast(pdata); diff --git a/gfx/graphite2/src/Pass.cpp b/gfx/graphite2/src/Pass.cpp index a44648a85a62..743e260d6111 100644 --- a/gfx/graphite2/src/Pass.cpp +++ b/gfx/graphite2/src/Pass.cpp @@ -46,16 +46,16 @@ Pass::Pass() m_rules(0), m_ruleMap(0), m_startStates(0), - m_sTable(0), + m_transitions(0), m_states(0), m_flags(0), m_iMaxLoop(0), m_numGlyphs(0), m_numRules(0), - m_sRows(0), - m_sTransition(0), - m_sSuccess(0), - m_sColumns(0), + m_numStates(0), + m_numTransition(0), + m_numSuccess(0), + m_numColumns(0), m_minPreCtxt(0), m_maxPreCtxt(0) { @@ -65,14 +65,14 @@ Pass::~Pass() { free(m_cols); free(m_startStates); - free(m_sTable); + free(m_transitions); free(m_states); free(m_ruleMap); delete [] m_rules; } -bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, const Face & face) +bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, GR_MAYBE_UNUSED const Face & face) { const byte * p = pass_start, * const pass_end = p + pass_length; @@ -89,33 +89,34 @@ bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t su * const rcCode = pass_start + be::read(p) - subtable_base, * const aCode = pass_start + be::read(p) - subtable_base; be::skip(p); - m_sRows = be::read(p); - m_sTransition = be::read(p); - m_sSuccess = be::read(p); - m_sColumns = be::read(p); + m_numStates = be::read(p); + m_numTransition = be::read(p); + m_numSuccess = be::read(p); + m_numColumns = be::read(p); numRanges = be::read(p); be::skip(p, 3); // skip searchRange, entrySelector & rangeShift. assert(p - pass_start == 40); // Perform some sanity checks. - if ( m_sTransition > m_sRows - || m_sSuccess > m_sRows - || m_sSuccess + m_sTransition < m_sRows + if ( m_numTransition > m_numStates + || m_numSuccess > m_numStates + || m_numSuccess + m_numTransition < m_numStates || numRanges == 0) return false; + m_successStart = m_numStates - m_numSuccess; if (p + numRanges * 6 - 4 > pass_end) return false; m_numGlyphs = be::peek(p + numRanges * 6 - 4) + 1; // Calculate the start of various arrays. const byte * const ranges = p; be::skip(p, numRanges*3); const byte * const o_rule_map = p; - be::skip(p, m_sSuccess + 1); + be::skip(p, m_numSuccess + 1); // More sanity checks - if (reinterpret_cast(o_rule_map + m_sSuccess*sizeof(uint16)) > pass_end + if (reinterpret_cast(o_rule_map + m_numSuccess*sizeof(uint16)) > pass_end || p > pass_end) return false; - const size_t numEntries = be::peek(o_rule_map + m_sSuccess*sizeof(uint16)); + const size_t numEntries = be::peek(o_rule_map + m_numSuccess*sizeof(uint16)); const byte * const rule_map = p; be::skip(p, numEntries); @@ -138,7 +139,7 @@ bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t su const uint16 * const o_actions = reinterpret_cast(p); be::skip(p, m_numRules + 1); const byte * const states = p; - be::skip(p, m_sTransition*m_sColumns); + be::skip(p, m_numTransition*m_numColumns); be::skip(p); // skip reserved byte if (p != pcCode || p >= pass_end) return false; be::skip(p, pass_constraint_len); @@ -161,7 +162,10 @@ bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t su if (!readRanges(ranges, numRanges)) return false; if (!readRules(rule_map, numEntries, precontext, sort_keys, o_constraint, rcCode, o_actions, aCode, face)) return false; - return readStates(start_states, states, o_rule_map); +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _states_cat(face.tele.states); +#endif + return readStates(start_states, states, o_rule_map, face); } @@ -225,36 +229,43 @@ bool Pass::readRules(const byte * rule_map, const size_t num_entries, static int cmpRuleEntry(const void *a, const void *b) { return (*(RuleEntry *)a < *(RuleEntry *)b ? -1 : (*(RuleEntry *)b < *(RuleEntry *)a ? 1 : 0)); } -bool Pass::readStates(const byte * starts, const byte *states, const byte * o_rule_map) +bool Pass::readStates(const byte * starts, const byte *states, const byte * o_rule_map, GR_MAYBE_UNUSED const Face & face) { - m_startStates = gralloc(m_maxPreCtxt - m_minPreCtxt + 1); - m_states = gralloc(m_sRows); - m_sTable = gralloc(m_sTransition * m_sColumns); +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _states_cat(face.tele.starts); +#endif + m_startStates = gralloc(m_maxPreCtxt - m_minPreCtxt + 1); +#ifdef GRAPHITE2_TELEMETRY + telemetry::set_category(face.tele.states); +#endif + m_states = gralloc(m_numStates); +#ifdef GRAPHITE2_TELEMETRY + telemetry::set_category(face.tele.transitions); +#endif + m_transitions = gralloc(m_numTransition * m_numColumns); - if (!m_startStates || !m_states || !m_sTable) return false; + if (!m_startStates || !m_states || !m_transitions) return false; // load start states - for (State * * s = m_startStates, - * * const s_end = s + m_maxPreCtxt - m_minPreCtxt + 1; s != s_end; ++s) + for (uint16 * s = m_startStates, + * const s_end = s + m_maxPreCtxt - m_minPreCtxt + 1; s != s_end; ++s) { - *s = m_states + be::read(starts); - if (*s < m_states || *s >= m_states + m_sRows) return false; // true; + *s = be::read(starts); + if (*s >= m_numStates) return false; // true; } // load state transition table. - for (State * * t = m_sTable, - * * const t_end = t + m_sTransition*m_sColumns; t != t_end; ++t) + for (uint16 * t = m_transitions, + * const t_end = t + m_numTransition*m_numColumns; t != t_end; ++t) { - *t = m_states + be::read(states); - if (*t < m_states || *t >= m_states + m_sRows) return false; + *t = be::read(states); + if (*t >= m_numStates) return false; } State * s = m_states, - * const transitions_end = m_states + m_sTransition, - * const success_begin = m_states + m_sRows - m_sSuccess; - const RuleEntry * rule_map_end = m_ruleMap + be::peek(o_rule_map + m_sSuccess*sizeof(uint16)); - for (size_t n = m_sRows; n; --n, ++s) + * const success_begin = m_states + m_numStates - m_numSuccess; + const RuleEntry * rule_map_end = m_ruleMap + be::peek(o_rule_map + m_numSuccess*sizeof(uint16)); + for (size_t n = m_numStates; n; --n, ++s) { - s->transitions = s < transitions_end ? m_sTable + (s-m_states)*m_sColumns : 0; RuleEntry * const begin = s < success_begin ? 0 : m_ruleMap + be::read(o_rule_map), * const end = s < success_begin ? 0 : m_ruleMap + be::peek(o_rule_map); @@ -279,7 +290,7 @@ bool Pass::readRanges(const byte * ranges, size_t num_ranges) * ci_end = m_cols + be::read(ranges) + 1, col = be::read(ranges); - if (ci >= ci_end || ci_end > m_cols+m_numGlyphs || col >= m_sColumns) + if (ci >= ci_end || ci_end > m_cols+m_numGlyphs || col >= m_numColumns) return false; // A glyph must only belong to one column at a time @@ -322,31 +333,30 @@ void Pass::runGraphite(Machine & m, FiniteStateMachine & fsm) const } while (s); } -inline uint16 Pass::glyphToCol(const uint16 gid) const -{ - return gid < m_numGlyphs ? m_cols[gid] : 0xffffU; -} - bool Pass::runFSM(FiniteStateMachine& fsm, Slot * slot) const { fsm.reset(slot, m_maxPreCtxt); if (fsm.slots.context() < m_minPreCtxt) return false; - const State * state = m_startStates[m_maxPreCtxt - fsm.slots.context()]; + uint16 state = m_startStates[m_maxPreCtxt - fsm.slots.context()]; + uint8 free_slots = SlotMap::MAX_SLOTS; do { fsm.slots.pushSlot(slot); - if (fsm.slots.size() >= SlotMap::MAX_SLOTS) return false; - const uint16 col = glyphToCol(slot->gid()); - if (col == 0xffffU || !state->is_transition()) return true; + if (--free_slots == 0 + || slot->gid() >= m_numGlyphs + || m_cols[slot->gid()] == 0xffffU + || state >= m_numTransition) + return free_slots != 0; - state = state->transitions[col]; - if (state->is_success()) - fsm.rules.accumulate_rules(*state); + const uint16 * transitions = m_transitions + state*m_numColumns; + state = transitions[m_cols[slot->gid()]]; + if (state >= m_successStart) + fsm.rules.accumulate_rules(m_states[state]); slot = slot->next(); - } while (state != m_states && slot); + } while (state != 0 && slot); fsm.slots.pushSlot(slot); return true; diff --git a/gfx/graphite2/src/Segment.cpp b/gfx/graphite2/src/Segment.cpp index 6e6687c3900a..ccce282bf3f4 100644 --- a/gfx/graphite2/src/Segment.cpp +++ b/gfx/graphite2/src/Segment.cpp @@ -52,6 +52,7 @@ Segment::Segment(unsigned int numchars, const Face* face, uint32 script, int tex m_bufSize(numchars + 10), m_numGlyphs(numchars), m_numCharinfo(numchars), + m_passBits(m_silf->aPassBits() ? -1 : 0), m_defaultOriginal(0), m_dir(textDir) { @@ -128,6 +129,7 @@ void Segment::append(const Segment &other) m_numGlyphs += other.m_numGlyphs; m_advance = m_advance + other.m_advance; m_bbox = m_bbox.widen(bbox); + m_passBits &= other.passBits(); } #endif // GRAPHITE2_NSEGCACHE @@ -150,6 +152,9 @@ void Segment::appendSlot(int id, int cid, int gid, int iFeats, size_t coffset) aSlot->prev(m_last); m_last = aSlot; if (!m_first) m_first = aSlot; + if (theGlyph && m_silf->aPassBits()) + m_passBits &= theGlyph->attrs()[m_silf->aPassBits()] + | (m_silf->numPasses() > 16 ? (theGlyph->attrs()[m_silf->aPassBits() + 1] << 16) : 0); } Slot *Segment::newSlot() @@ -185,6 +190,13 @@ void Segment::freeSlot(Slot *aSlot) { if (m_last == aSlot) m_last = aSlot->prev(); if (m_first == aSlot) m_first = aSlot->next(); + if (aSlot->attachedTo()) + aSlot->attachedTo()->removeChild(aSlot); + while (aSlot->firstChild()) + { + aSlot->firstChild()->attachTo(NULL); + aSlot->removeChild(aSlot->firstChild()); + } // reset the slot incase it is reused ::new (aSlot) Slot; memset(aSlot->userAttrs(), 0, m_silf->numUser() * sizeof(int16)); diff --git a/gfx/graphite2/src/Silf.cpp b/gfx/graphite2/src/Silf.cpp index 53c138419375..74b8003611cc 100644 --- a/gfx/graphite2/src/Silf.cpp +++ b/gfx/graphite2/src/Silf.cpp @@ -55,6 +55,7 @@ Silf::Silf() throw() m_aUser(0), m_aBidi(0), m_aMirror(0), + m_aPassBits(0), m_iMaxComp(0), m_aLig(0), m_numPseudo(0), @@ -111,7 +112,7 @@ bool Silf::readGraphite(const byte * const silf_start, size_t lSilf, const Face& m_aBreak = be::read(p); m_aBidi = be::read(p); m_aMirror = be::read(p); - be::skip(p); // skip reserved stuff + m_aPassBits = be::read(p); // Read Justification levels. m_numJusts = be::read(p); @@ -374,7 +375,8 @@ bool Silf::runGraphite(Segment *seg, uint8 firstPass, uint8 lastPass) const #endif // test whether to reorder, prepare for positioning - m_passes[i].runGraphite(m, fsm); + if (i >= 32 || (seg->passBits() & (1 << i)) == 0) + m_passes[i].runGraphite(m, fsm); // only subsitution passes can change segment length, cached subsegments are short for their text if (m.status() != vm::Machine::finished || (i < m_pPass && (seg->slotCount() > initSize * MAX_SEG_GROWTH_FACTOR diff --git a/gfx/graphite2/src/Slot.cpp b/gfx/graphite2/src/Slot.cpp index f78fee73264e..21618f7e7df3 100644 --- a/gfx/graphite2/src/Slot.cpp +++ b/gfx/graphite2/src/Slot.cpp @@ -87,7 +87,7 @@ Position Slot::finalise(const Segment *seg, const Font *font, Position & base, R { if (attrLevel && m_attLevel > attrLevel) return Position(0, 0); float scale = 1.0; - Position shift = m_shift + Position(m_just, 0); + Position shift(m_shift.x * ((seg->dir() & 1) * -2 + 1) + m_just, m_shift.y); float tAdvance = m_advance.x + m_just; const GlyphFace * glyphFace = seg->getFace()->glyphs().glyphSafe(glyph()); if (font) @@ -144,7 +144,7 @@ Position Slot::finalise(const Segment *seg, const Font *font, Position & base, R return res; } -uint32 Slot::clusterMetric(const Segment *seg, uint8 metric, uint8 attrLevel) +int32 Slot::clusterMetric(const Segment *seg, uint8 metric, uint8 attrLevel) { Position base; Rect bbox = seg->theGlyphBBoxTemporary(gid()); @@ -247,7 +247,9 @@ void Slot::setAttr(Segment *seg, attrCode ind, uint8 subindex, int16 value, cons if (idx < map.size() && map[idx]) { Slot *other = map[idx]; - if (other != this && other->child(this)) + if (other == this) break; + if (m_parent) m_parent->removeChild(this); + if (other->child(this)) { attachTo(other); if (((seg->dir() & 1) != 0) ^ (idx > subindex)) @@ -313,7 +315,7 @@ int Slot::getJustify(const Segment *seg, uint8 level, uint8 subindex) const void Slot::setJustify(Segment *seg, uint8 level, uint8 subindex, int16 value) { - if (level >= seg->silf()->numJustLevels()) return; + if (level && level >= seg->silf()->numJustLevels()) return; if (!m_justs) { SlotJustify *j = seg->newJustify(); @@ -345,6 +347,34 @@ bool Slot::sibling(Slot *ap) return true; } +bool Slot::removeChild(Slot *ap) +{ + if (this == ap || !m_child) return false; + else if (ap == m_child) + { + Slot *nSibling = m_child->nextSibling(); + m_child->sibling(NULL); + m_child = nSibling; + return true; + } + else + return m_child->removeSibling(ap); + return true; +} + +bool Slot::removeSibling(Slot *ap) +{ + if (this == ap || !m_sibling) return false; + else if (ap == m_sibling) + { + m_sibling = m_sibling->nextSibling(); + return true; + } + else + return m_sibling->removeSibling(ap); + return true; +} + void Slot::setGlyph(Segment *seg, uint16 glyphid, const GlyphFace * theGlyph) { m_glyphid = glyphid; @@ -365,6 +395,8 @@ void Slot::setGlyph(Segment *seg, uint16 glyphid, const GlyphFace * theGlyph) if (aGlyph) theGlyph = aGlyph; } m_advance = Position(theGlyph->theAdvance().x, 0.); + if (seg->silf()->aPassBits()) + seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()]); } void Slot::floodShift(Position adj) diff --git a/gfx/graphite2/src/gr_face.cpp b/gfx/graphite2/src/gr_face.cpp index e0f243355229..caf16b33a35b 100644 --- a/gfx/graphite2/src/gr_face.cpp +++ b/gfx/graphite2/src/gr_face.cpp @@ -38,6 +38,9 @@ namespace { bool load_face(Face & face, unsigned int options) { +#ifdef GRAPHITE2_TELEMETRY + telemetry::category _misc_cat(face.tele.misc); +#endif Face::Table silf(face, Tag::Silf); if (silf) options &= ~gr_face_dumbRendering; else if (!(options & gr_face_dumbRendering)) diff --git a/gfx/graphite2/src/gr_logging.cpp b/gfx/graphite2/src/gr_logging.cpp index 9ccd7058d9a2..f989effd3d1b 100644 --- a/gfx/graphite2/src/gr_logging.cpp +++ b/gfx/graphite2/src/gr_logging.cpp @@ -38,7 +38,6 @@ of the License or (at your option) any later version. using namespace graphite2; - extern "C" { @@ -58,19 +57,23 @@ bool gr_start_logging(gr_face * face, const char *log_path) log = _wfopen(wlog_path, L"wt"); free(wlog_path); -#else +#else // _WIN32 FILE *log = fopen(log_path, "wt"); -#endif +#endif // _WIN32 if (!log) return false; face->setLogger(log); if (!face->logger()) return false; *face->logger() << json::array; - return true; -#else - return false; +#ifdef GRAPHITE2_TELEMETRY + *face->logger() << face->tele; #endif + + return true; +#else // GRAPHITE2_NTRACING + return false; +#endif // GRAPHITE2_NTRACING } bool graphite_start_logging(FILE * /* log */, GrLogMask /* mask */) @@ -112,8 +115,37 @@ void graphite_stop_logging() } // extern "C" +#ifdef GRAPHITE2_TELEMETRY +size_t * graphite2::telemetry::_category = 0UL; +#endif + #if !defined GRAPHITE2_NTRACING +#ifdef GRAPHITE2_TELEMETRY + +json & graphite2::operator << (json & j, const telemetry & t) throw() +{ + j << json::object + << "type" << "telemetry" + << "silf" << t.silf + << "states" << t.states + << "starts" << t.starts + << "transitions" << t.transitions + << "glyphs" << t.glyph + << "code" << t.code + << "misc" << t.misc + << "total" << (t.silf + t.states + t.starts + t.transitions + t.glyph + t.code + t.misc) + << json::close; + return j; +} +#else +json & graphite2::operator << (json & j, const telemetry &) throw() +{ + return j; +} +#endif + + json & graphite2::operator << (json & j, const CharInfo & ci) throw() { return j << json::object diff --git a/gfx/graphite2/src/inc/Face.h b/gfx/graphite2/src/inc/Face.h index d5436482239b..adf8f77e5917 100644 --- a/gfx/graphite2/src/inc/Face.h +++ b/gfx/graphite2/src/inc/Face.h @@ -43,6 +43,7 @@ class GlyphCache; class NameTable; class json; + using TtfUtil::Tag; // These are the actual tags, as distinct from the consecutive IDs in TtfUtil.h @@ -103,6 +104,10 @@ protected: private: uint16 m_ascent, m_descent; +#ifdef GRAPHITE2_TELEMETRY +public: + mutable telemetry tele; +#endif }; diff --git a/gfx/graphite2/src/inc/Machine.h b/gfx/graphite2/src/inc/Machine.h index 655d60696849..f6af44329fa7 100644 --- a/gfx/graphite2/src/inc/Machine.h +++ b/gfx/graphite2/src/inc/Machine.h @@ -36,7 +36,7 @@ of the License or (at your option) any later version. #include "inc/Main.h" #if defined(__GNUC__) -#if defined(__clang__) +#if defined(__clang__) || (__GNUC__ * 100 + __GNUC_MINOR__ * 10) < 430 #define HOT #if defined(__x86_64) #define REGPARM(n) __attribute__((regparm(n))) diff --git a/gfx/graphite2/src/inc/Main.h b/gfx/graphite2/src/inc/Main.h index 674af80e854b..0717693d6896 100644 --- a/gfx/graphite2/src/inc/Main.h +++ b/gfx/graphite2/src/inc/Main.h @@ -44,15 +44,55 @@ typedef gr_int16 int16; typedef gr_int32 int32; typedef size_t uintptr; +#if GRAPHITE2_TELEMETRY +struct telemetry +{ + class category; + + static size_t * _category; + static void set_category(size_t & t) throw() { _category = &t; } + static void stop() throw() { _category = 0; } + static void count_bytes(size_t n) throw() { if (_category) *_category += n; } + + size_t misc, + silf, + glyph, + code, + states, + starts, + transitions; + + telemetry() : misc(0), silf(0), glyph(0), code(0), states(0), starts(0), transitions(0) {} +}; + +class telemetry::category +{ + size_t * _prev; +public: + category(size_t & t) : _prev(_category) { _category = &t; } + ~category() { _category = _prev; } +}; + +#else +struct telemetry {}; +#endif + // typesafe wrapper around malloc for simple types // use free(pointer) to deallocate + template T * gralloc(size_t n) { +#if GRAPHITE2_TELEMETRY + telemetry::count_bytes(sizeof(T) * n); +#endif return reinterpret_cast(malloc(sizeof(T) * n)); } template T * grzeroalloc(size_t n) { +#if GRAPHITE2_TELEMETRY + telemetry::count_bytes(sizeof(T) * n); +#endif return reinterpret_cast(calloc(n, sizeof(T))); } @@ -71,9 +111,9 @@ inline T max(const T a, const T b) } // namespace graphite2 #define CLASS_NEW_DELETE \ - void * operator new (size_t size){ return malloc(size);} \ + void * operator new (size_t size){ return gralloc(size);} \ void * operator new (size_t, void * p) throw() { return p; } \ - void * operator new[] (size_t size) {return malloc(size);} \ + void * operator new[] (size_t size) {return gralloc(size);} \ void * operator new[] (size_t, void * p) throw() { return p; } \ void operator delete (void * p) throw() { free(p);} \ void operator delete (void *, void *) throw() {} \ diff --git a/gfx/graphite2/src/inc/Pass.h b/gfx/graphite2/src/inc/Pass.h index 6554d937f0ec..5fea65a289b4 100644 --- a/gfx/graphite2/src/inc/Pass.h +++ b/gfx/graphite2/src/inc/Pass.h @@ -61,7 +61,7 @@ private: const uint16 * o_constraint, const byte *constraint_data, const uint16 * o_action, const byte * action_data, const Face &); - bool readStates(const byte * starts, const byte * states, const byte * o_rule_map); + bool readStates(const byte * starts, const byte * states, const byte * o_rule_map, const Face &); bool readRanges(const byte * ranges, size_t num_ranges); uint16 glyphToCol(const uint16 gid) const; bool runFSM(FiniteStateMachine & fsm, Slot * slot) const; @@ -72,18 +72,19 @@ private: uint16 * m_cols; Rule * m_rules; // rules RuleEntry * m_ruleMap; - State * * m_startStates; // prectxt length - State * * m_sTable; + uint16 * m_startStates; // prectxt length + uint16 * m_transitions; State * m_states; byte m_flags; byte m_iMaxLoop; uint16 m_numGlyphs; uint16 m_numRules; - uint16 m_sRows; - uint16 m_sTransition; - uint16 m_sSuccess; - uint16 m_sColumns; + uint16 m_numStates; + uint16 m_numTransition; + uint16 m_numSuccess; + uint16 m_successStart; + uint16 m_numColumns; byte m_minPreCtxt; byte m_maxPreCtxt; vm::Machine::Code m_cPConstraint; diff --git a/gfx/graphite2/src/inc/Rule.h b/gfx/graphite2/src/inc/Rule.h index 69442658e4d2..491d985e3a70 100644 --- a/gfx/graphite2/src/inc/Rule.h +++ b/gfx/graphite2/src/inc/Rule.h @@ -64,11 +64,11 @@ struct RuleEntry inline bool operator < (const RuleEntry &r) const - { - const unsigned short lsort = rule->sort, rsort = r.rule->sort; + { + const unsigned short lsort = rule->sort, rsort = r.rule->sort; return lsort > rsort || (lsort == rsort && rule < r.rule); } - + inline bool operator == (const RuleEntry &r) const { @@ -81,29 +81,14 @@ struct State { const RuleEntry * rules, * rules_end; - const State * const * transitions; - size_t size() const; - bool is_success() const; - bool is_transition() const; + bool empty() const; }; inline -size_t State::size() const +bool State::empty() const { - return rules_end - rules; -} - -inline -bool State::is_success() const -{ - return (rules != NULL); -} - -inline -bool State::is_transition() const -{ - return (transitions != NULL); + return rules_end == rules; } @@ -222,12 +207,13 @@ inline void FiniteStateMachine::Rules::accumulate_rules(const State &state) { // Only bother if there are rules in the State object. - if (state.size() == 0) return; + if (state.empty()) return; // Merge the new sorted rules list into the current sorted result set. const RuleEntry * lre = begin(), * rre = state.rules; RuleEntry * out = m_rules + (m_begin == m_rules)*MAX_RULES; - const RuleEntry * lrend = out + MAX_RULES; + const RuleEntry * const lrend = out + MAX_RULES, + * const rrend = state.rules_end; m_begin = out; while (lre != end() && out != lrend) { @@ -235,14 +221,14 @@ void FiniteStateMachine::Rules::accumulate_rules(const State &state) else if (*rre < *lre) { *out++ = *rre++; } else { *out++ = *lre++; ++rre; } - if (rre == state.rules_end) + if (rre == rrend) { while (lre != end() && out != lrend) { *out++ = *lre++; } m_end = out; return; } } - while (rre != state.rules_end && out != lrend) { *out++ = *rre++; } + while (rre != rrend && out != lrend) { *out++ = *rre++; } m_end = out; } @@ -289,7 +275,7 @@ void SlotMap::reset(Slot & slot, short unsigned int ctxt) inline void SlotMap::pushSlot(Slot*const slot) { - m_slot_map[m_size++ + 1] = slot; + m_slot_map[++m_size] = slot; } inline diff --git a/gfx/graphite2/src/inc/Segment.h b/gfx/graphite2/src/inc/Segment.h index 9e9dc62e7bc7..8ddeeb8106b9 100644 --- a/gfx/graphite2/src/inc/Segment.h +++ b/gfx/graphite2/src/inc/Segment.h @@ -123,8 +123,10 @@ public: int addFeatures(const Features& feats) { m_feats.push_back(feats); return m_feats.size() - 1; } uint32 getFeature(int index, uint8 findex) const { const FeatureRef* pFR=m_face->theSill().theFeatureMap().featureRef(findex); if (!pFR) return 0; else return pFR->getFeatureVal(m_feats[index]); } void dir(int8 val) { m_dir = val; } - uint16 glyphAttr(uint16 gid, uint16 gattr) const { const GlyphFace * p = m_face->glyphs().glyphSafe(gid); return p ? p->attrs()[gattr] : 0; } - uint16 getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const; + unsigned int passBits() const { return m_passBits; } + void mergePassBits(const unsigned int val) { m_passBits &= val; } + int16 glyphAttr(uint16 gid, uint16 gattr) const { const GlyphFace * p = m_face->glyphs().glyphSafe(gid); return p ? p->attrs()[gattr] : 0; } + int32 getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const; float glyphAdvance(uint16 gid) const { return m_face->glyphs().glyph(gid)->theAdvance().x; } const Rect &theGlyphBBoxTemporary(uint16 gid) const { return m_face->glyphs().glyph(gid)->theBBox(); } //warning value may become invalid when another glyph is accessed Slot *findRoot(Slot *is) const { return is->attachedTo() ? findRoot(is->attachedTo()) : is; } @@ -163,7 +165,8 @@ private: Slot * m_last; // last slot in segment unsigned int m_bufSize, // how big a buffer to create when need more slots m_numGlyphs, - m_numCharinfo; // size of the array and number of input characters + m_numCharinfo, // size of the array and number of input characters + m_passBits; // if bit set then skip pass int m_defaultOriginal; // number of whitespace chars in the string int8 m_dir; }; @@ -181,7 +184,7 @@ void Segment::finalise(const Font *font) } inline -uint16 Segment::getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const { +int32 Segment::getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const { if (attrLevel > 0) { Slot *is = findRoot(iSlot); diff --git a/gfx/graphite2/src/inc/Silf.h b/gfx/graphite2/src/inc/Silf.h index 652f084c8ec6..526d9ba30bde 100644 --- a/gfx/graphite2/src/inc/Silf.h +++ b/gfx/graphite2/src/inc/Silf.h @@ -82,6 +82,7 @@ public: uint8 aPseudo() const { return m_aPseudo; } uint8 aBreak() const { return m_aBreak; } uint8 aMirror() const {return m_aMirror; } + uint8 aPassBits() const { return m_aPassBits; } uint8 substitutionPass() const { return m_sPass; } uint8 positionPass() const { return m_pPass; } uint8 justificationPass() const { return m_jPass; } @@ -111,7 +112,7 @@ private: uint8 m_sPass, m_pPass, m_jPass, m_bPass, m_flags; - uint8 m_aPseudo, m_aBreak, m_aUser, m_aBidi, m_aMirror, + uint8 m_aPseudo, m_aBreak, m_aUser, m_aBidi, m_aMirror, m_aPassBits, m_iMaxComp; uint16 m_aLig, m_numPseudo, diff --git a/gfx/graphite2/src/inc/Slot.h b/gfx/graphite2/src/inc/Slot.h index 285d7f105670..3f0c473889a2 100644 --- a/gfx/graphite2/src/inc/Slot.h +++ b/gfx/graphite2/src/inc/Slot.h @@ -125,7 +125,9 @@ public: bool child(Slot *ap); Slot* nextSibling() const { return m_sibling; } bool sibling(Slot *ap); - uint32 clusterMetric(const Segment* seg, uint8 metric, uint8 attrLevel); + bool removeChild(Slot *ap); + bool removeSibling(Slot *ap); + int32 clusterMetric(const Segment* seg, uint8 metric, uint8 attrLevel); void positionShift(Position a) { m_position += a; } void floodShift(Position adj); float just() const { return m_just; } diff --git a/gfx/graphite2/src/inc/debug.h b/gfx/graphite2/src/inc/debug.h index 6c733c6a5a48..ba77cb191514 100644 --- a/gfx/graphite2/src/inc/debug.h +++ b/gfx/graphite2/src/inc/debug.h @@ -52,10 +52,13 @@ struct objectid objectid(const Segment * const p) throw(); }; + json & operator << (json & j, const Position &) throw(); json & operator << (json & j, const CharInfo &) throw(); json & operator << (json & j, const dslot &) throw(); json & operator << (json & j, const objectid &) throw(); +json & operator << (json & j, const telemetry &) throw(); + inline diff --git a/gfx/graphite2/src/inc/opcodes.h b/gfx/graphite2/src/inc/opcodes.h index 6fad29f66fcc..835e89351ff6 100644 --- a/gfx/graphite2/src/inc/opcodes.h +++ b/gfx/graphite2/src/inc/opcodes.h @@ -443,7 +443,7 @@ STARTOP(push_glyph_attr_obs) const int slot_ref = int8(param[1]); slotref slot = slotat(slot_ref); if (slot) - push(seg.glyphAttr(slot->gid(), glyph_attr)); + push(int32(seg.glyphAttr(slot->gid(), glyph_attr))); ENDOP STARTOP(push_glyph_metric) @@ -477,7 +477,7 @@ STARTOP(push_att_to_gattr_obs) { slotref att = slot->attachedTo(); if (att) slot = att; - push(seg.glyphAttr(slot->gid(), glyph_attr)); + push(int32(seg.glyphAttr(slot->gid(), glyph_attr))); } ENDOP @@ -491,7 +491,7 @@ STARTOP(push_att_to_glyph_metric) { slotref att = slot->attachedTo(); if (att) slot = att; - push(seg.getGlyphMetric(slot, glyph_attr, attr_level)); + push(int32(seg.getGlyphMetric(slot, glyph_attr, attr_level))); } ENDOP @@ -616,7 +616,7 @@ STARTOP(push_glyph_attr) const int slot_ref = int8(param[2]); slotref slot = slotat(slot_ref); if (slot) - push(seg.glyphAttr(slot->gid(), glyph_attr)); + push(int32(seg.glyphAttr(slot->gid(), glyph_attr))); ENDOP STARTOP(push_att_to_glyph_attr) @@ -629,7 +629,7 @@ STARTOP(push_att_to_glyph_attr) { slotref att = slot->attachedTo(); if (att) slot = att; - push(seg.glyphAttr(slot->gid(), glyph_attr)); + push(int32(seg.glyphAttr(slot->gid(), glyph_attr))); } ENDOP diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index b3af07bf7f94..f7a5667669ea 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -10,6 +10,7 @@ #include "mozilla/layers/PLayerTransaction.h" #include "mozilla/layers/LayerManagerComposite.h" #include "mozilla/Telemetry.h" +#include "CompositableHost.h" #include "ImageLayers.h" #include "ImageContainer.h" @@ -981,6 +982,9 @@ static nsACString& PrintInfo(nsACString& aTo, LayerComposite* aLayerComposite); template void WriteSnapshotLinkToDumpFile(T* aObj, FILE* aFile) { + if (!aObj) { + return; + } nsCString string(aObj->Name()); string.Append("-"); string.AppendInt((uint64_t)aObj); @@ -1029,6 +1033,9 @@ Layer::Dump(FILE* aFile, const char* aPrefix, bool aDumpHtml) fprintf(aFile, ">"); } DumpSelf(aFile, aPrefix); + if (AsLayerComposite() && AsLayerComposite()->GetCompositableHost()) { + AsLayerComposite()->GetCompositableHost()->Dump(aFile, aPrefix, aDumpHtml); + } if (aDumpHtml) { fprintf(aFile, ""); } @@ -1036,7 +1043,7 @@ Layer::Dump(FILE* aFile, const char* aPrefix, bool aDumpHtml) if (Layer* mask = GetMaskLayer()) { nsAutoCString pfx(aPrefix); pfx += " Mask layer: "; - mask->Dump(aFile, pfx.get()); + mask->Dump(aFile, pfx.get(), aDumpHtml); } if (Layer* kid = GetFirstChild()) { @@ -1045,7 +1052,7 @@ Layer::Dump(FILE* aFile, const char* aPrefix, bool aDumpHtml) if (aDumpHtml) { fprintf(aFile, "
    "); } - kid->Dump(aFile, pfx.get()); + kid->Dump(aFile, pfx.get(), aDumpHtml); if (aDumpHtml) { fprintf(aFile, "
"); } diff --git a/gfx/layers/TiledLayerBuffer.h b/gfx/layers/TiledLayerBuffer.h index 888eb5f86322..e6fe6ea07753 100644 --- a/gfx/layers/TiledLayerBuffer.h +++ b/gfx/layers/TiledLayerBuffer.h @@ -145,6 +145,10 @@ public: } bool IsLowPrecision() const { return mResolution < 1; } + typedef Tile* Iterator; + Iterator TilesBegin() { return mRetainedTiles.Elements(); } + Iterator TilesEnd() { return mRetainedTiles.Elements() + mRetainedTiles.Length(); } + protected: // The implementor should call Update() to change // the new valid region. This implementation will call diff --git a/gfx/layers/basic/BasicCompositor.cpp b/gfx/layers/basic/BasicCompositor.cpp index 960ac2a4e615..0b27234306b9 100644 --- a/gfx/layers/basic/BasicCompositor.cpp +++ b/gfx/layers/basic/BasicCompositor.cpp @@ -59,6 +59,14 @@ protected: return true; } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE { + if (!mThebesImage) { + mThebesImage = mThebesSurface->GetAsImageSurface(); + } + nsRefPtr result = mThebesImage; + return result.forget(); + } + BasicCompositor *mCompositor; RefPtr mSurface; nsRefPtr mThebesImage; diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index f9dc486a52fe..b5ad175470b7 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -52,7 +52,7 @@ void CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) { if (!mTextureClient) { - mTextureClient = CreateTextureClient(TEXTURE_SHMEM); + mTextureClient = CreateTextureClient(TEXTURE_CONTENT); MOZ_ASSERT(mTextureClient, "Failed to create texture client"); } diff --git a/gfx/layers/composite/CanvasLayerComposite.cpp b/gfx/layers/composite/CanvasLayerComposite.cpp index ff74aaa66d13..18c1a0712a0f 100644 --- a/gfx/layers/composite/CanvasLayerComposite.cpp +++ b/gfx/layers/composite/CanvasLayerComposite.cpp @@ -11,6 +11,7 @@ #include "CanvasLayerComposite.h" #include "ImageHost.h" +#include "gfxUtils.h" #include "gfx2DGlue.h" using namespace mozilla; @@ -61,6 +62,13 @@ CanvasLayerComposite::RenderLayer(const nsIntPoint& aOffset, mCompositor->MakeCurrent(); +#ifdef MOZ_DUMP_PAINTING + if (gfxUtils::sDumpPainting) { + nsRefPtr surf = mImageHost->GetAsSurface(); + WriteSnapshotToDumpFile(this, surf); + } +#endif + gfxPattern::GraphicsFilter filter = mFilter; #ifdef ANDROID // Bug 691354 diff --git a/gfx/layers/composite/CompositableHost.cpp b/gfx/layers/composite/CompositableHost.cpp index 76e2c78bd8b5..654ed32c597e 100644 --- a/gfx/layers/composite/CompositableHost.cpp +++ b/gfx/layers/composite/CompositableHost.cpp @@ -77,6 +77,19 @@ CompositableHost::Create(const TextureInfo& aTextureInfo) } } +void +CompositableHost::DumpTextureHost(FILE* aFile, TextureHost* aTexture) +{ + if (!aTexture) { + return; + } + nsRefPtr surf = aTexture->GetAsSurface(); + if (!surf) { + return; + } + surf->DumpAsDataURL(aFile ? aFile : stderr); +} + void CompositableParent::ActorDestroy(ActorDestroyReason why) { diff --git a/gfx/layers/composite/CompositableHost.h b/gfx/layers/composite/CompositableHost.h index 2df8a1e288e3..fd8b925fd3d5 100644 --- a/gfx/layers/composite/CompositableHost.h +++ b/gfx/layers/composite/CompositableHost.h @@ -194,6 +194,15 @@ public: SetCompositor(nullptr); } + virtual void Dump(FILE* aFile=NULL, + const char* aPrefix="", + bool aDumpHtml=false) { } + static void DumpTextureHost(FILE* aFile, TextureHost* aTexture); + +#ifdef MOZ_DUMP_PAINTING + virtual already_AddRefed GetAsSurface() { return nullptr; } +#endif + #ifdef MOZ_LAYERS_HAVE_LOG virtual void PrintInfo(nsACString& aTo, const char* aPrefix) { } #endif diff --git a/gfx/layers/composite/ContentHost.cpp b/gfx/layers/composite/ContentHost.cpp index 33c4479e3154..60ce1ff17d81 100644 --- a/gfx/layers/composite/ContentHost.cpp +++ b/gfx/layers/composite/ContentHost.cpp @@ -207,6 +207,35 @@ ContentHostBase::SetCompositor(Compositor* aCompositor) } } +void +ContentHostBase::Dump(FILE* aFile, + const char* aPrefix, + bool aDumpHtml) +{ + if (!aFile) { + aFile = stderr; + } + if (aDumpHtml) { + fprintf(aFile, "
    "); + } + if (mTextureHost) { + fprintf(aFile, aPrefix); + fprintf(aFile, aDumpHtml ? "
  • Front buffer
  • " : " "); + } + if (mTextureHostOnWhite) { + fprintf(aFile, aPrefix); + fprintf(aFile, aDumpHtml ? "
  • Front buffer on white
  • " : " "); + } + if (aDumpHtml) { + fprintf(aFile, "
"); + } + +} + ContentHostSingleBuffered::~ContentHostSingleBuffered() { DestroyTextures(); @@ -694,6 +723,35 @@ ContentHostDoubleBuffered::PrintInfo(nsACString& aTo, const char* aPrefix) } #endif +void +ContentHostDoubleBuffered::Dump(FILE* aFile, + const char* aPrefix, + bool aDumpHtml) +{ + ContentHostBase::Dump(aFile, aPrefix, aDumpHtml); + if (!aFile) { + aFile = stderr; + } + if (aDumpHtml) { + fprintf(aFile, "
    "); + } + if (mBackHost) { + fprintf(aFile, aPrefix); + fprintf(aFile, aDumpHtml ? "
  • Back buffer
  • " : " "); + } + if (mBackHostOnWhite) { + fprintf(aFile, aPrefix); + fprintf(aFile, aDumpHtml ? "
  • Back buffer on white
  • " : " "); + } + if (aDumpHtml) { + fprintf(aFile, "
"); + } + +} } // namespace } // namespace diff --git a/gfx/layers/composite/ContentHost.h b/gfx/layers/composite/ContentHost.h index 1ec20d9b382e..5e412909145c 100644 --- a/gfx/layers/composite/ContentHost.h +++ b/gfx/layers/composite/ContentHost.h @@ -34,10 +34,6 @@ public: const nsIntRegion& aOldValidRegionBack, nsIntRegion* aUpdatedRegionBack) = 0; -#ifdef MOZ_DUMP_PAINTING - virtual already_AddRefed Dump() { return nullptr; } -#endif - virtual void SetPaintWillResample(bool aResample) { } protected: @@ -93,12 +89,16 @@ public: virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE; #ifdef MOZ_DUMP_PAINTING - virtual already_AddRefed Dump() + virtual already_AddRefed GetAsSurface() { - return mTextureHost->Dump(); + return mTextureHost->GetAsSurface(); } #endif + virtual void Dump(FILE* aFile=NULL, + const char* aPrefix="", + bool aDumpHtml=false) MOZ_OVERRIDE; + virtual TextureHost* GetTextureHost() MOZ_OVERRIDE; virtual void SetPaintWillResample(bool aResample) { mPaintWillResample = aResample; } @@ -157,6 +157,10 @@ public: const TextureInfo& aTextureInfo) MOZ_OVERRIDE; virtual void DestroyTextures() MOZ_OVERRIDE; + virtual void Dump(FILE* aFile=NULL, + const char* aPrefix="", + bool aDumpHtml=false) MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual void PrintInfo(nsACString& aTo, const char* aPrefix); #endif diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp index d7f82cd57ce2..884f21e031ed 100644 --- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -180,5 +180,22 @@ ImageHostBuffered::MakeTextureHost(TextureIdentifier aTextureId, } } +void +ImageHostSingle::Dump(FILE* aFile, + const char* aPrefix, + bool aDumpHtml) +{ + if (!aFile) { + aFile = stderr; + } + if (mTextureHost) { + fprintf(aFile, aPrefix); + fprintf(aFile, aDumpHtml ? "
  • TextureHost: " + : "TextureHost: "); + DumpTextureHost(aFile, mTextureHost); + fprintf(aFile, aDumpHtml ? "
" : " "); + } +} + } } diff --git a/gfx/layers/composite/ImageHost.h b/gfx/layers/composite/ImageHost.h index e2c2b275f754..b64396134fe8 100644 --- a/gfx/layers/composite/ImageHost.h +++ b/gfx/layers/composite/ImageHost.h @@ -83,10 +83,21 @@ public: virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE; + virtual void Dump(FILE* aFile=NULL, + const char* aPrefix="", + bool aDumpHtml=false) MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual void PrintInfo(nsACString& aTo, const char* aPrefix); #endif +#ifdef MOZ_DUMP_PAINTING + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE + { + return mTextureHost->GetAsSurface(); + } +#endif + protected: virtual void MakeTextureHost(TextureIdentifier aTextureId, const SurfaceDescriptor& aSurface, diff --git a/gfx/layers/composite/ImageLayerComposite.cpp b/gfx/layers/composite/ImageLayerComposite.cpp index e894646b05f2..e6ca87ae2a8b 100644 --- a/gfx/layers/composite/ImageLayerComposite.cpp +++ b/gfx/layers/composite/ImageLayerComposite.cpp @@ -10,6 +10,7 @@ #include "ImageHost.h" #include "gfxImageSurface.h" #include "gfx2DGlue.h" +#include "gfxUtils.h" #include "mozilla/layers/Compositor.h" #include "mozilla/layers/CompositorTypes.h" // for TextureInfo @@ -73,6 +74,13 @@ ImageLayerComposite::RenderLayer(const nsIntPoint& aOffset, return; } +#ifdef MOZ_DUMP_PAINTING + if (gfxUtils::sDumpPainting) { + nsRefPtr surf = mImageHost->GetAsSurface(); + WriteSnapshotToDumpFile(this, surf); + } +#endif + mCompositor->MakeCurrent(); EffectChain effectChain; diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h index 7c6bc97890c2..9d93373d24f9 100644 --- a/gfx/layers/composite/TextureHost.h +++ b/gfx/layers/composite/TextureHost.h @@ -228,10 +228,6 @@ public: return mDeAllocator; } -#ifdef MOZ_DUMP_PAINTING - virtual already_AddRefed Dump() { return nullptr; } -#endif - bool operator== (const TextureHost& o) const { return GetIdentifier() == o.GetIdentifier(); @@ -247,6 +243,8 @@ public: mFlags & NeedsYFlip ? LAYER_RENDER_STATE_Y_FLIPPED : 0); } + virtual already_AddRefed GetAsSurface() = 0; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char *Name() = 0; virtual void PrintInfo(nsACString& aTo, const char* aPrefix); diff --git a/gfx/layers/composite/ThebesLayerComposite.cpp b/gfx/layers/composite/ThebesLayerComposite.cpp index 8501370000f5..a86000895c0a 100644 --- a/gfx/layers/composite/ThebesLayerComposite.cpp +++ b/gfx/layers/composite/ThebesLayerComposite.cpp @@ -101,7 +101,7 @@ ThebesLayerComposite::RenderLayer(const nsIntPoint& aOffset, #ifdef MOZ_DUMP_PAINTING if (gfxUtils::sDumpPainting) { - nsRefPtr surf = mBuffer->Dump(); + nsRefPtr surf = mBuffer->GetAsSurface(); WriteSnapshotToDumpFile(this, surf); } #endif diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp index 281f748c3255..1a6815cff518 100644 --- a/gfx/layers/composite/TiledContentHost.cpp +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -312,6 +312,30 @@ TiledContentHost::PrintInfo(nsACString& aTo, const char* aPrefix) } #endif +void +TiledContentHost::Dump(FILE* aFile, + const char* aPrefix, + bool aDumpHtml) +{ + if (!aFile) { + aFile = stderr; + } + + TiledLayerBufferComposite::Iterator it = mVideoMemoryTiledBuffer.TilesBegin(); + TiledLayerBufferComposite::Iterator stop = mVideoMemoryTiledBuffer.TilesEnd(); + if (aDumpHtml) { + fprintf(aFile, ""); + } +} } // namespace } // namespace diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h index b3e2762ca071..221c868d93c8 100644 --- a/gfx/layers/composite/TiledContentHost.h +++ b/gfx/layers/composite/TiledContentHost.h @@ -66,6 +66,7 @@ class TiledLayerBufferComposite friend class TiledLayerBuffer; public: + typedef TiledLayerBuffer::Iterator Iterator; TiledLayerBufferComposite() : mCompositor(nullptr) {} @@ -201,6 +202,10 @@ public: virtual void Attach(Layer* aLayer, Compositor* aCompositor) MOZ_OVERRIDE; + virtual void Dump(FILE* aFile=NULL, + const char* aPrefix="", + bool aDumpHtml=false) MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual void PrintInfo(nsACString& aTo, const char* aPrefix); #endif diff --git a/gfx/layers/d3d11/TextureD3D11.h b/gfx/layers/d3d11/TextureD3D11.h index 5416123bcf75..c7283310ce1b 100644 --- a/gfx/layers/d3d11/TextureD3D11.h +++ b/gfx/layers/d3d11/TextureD3D11.h @@ -122,6 +122,11 @@ public: virtual bool Lock() MOZ_OVERRIDE { return true; } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE + { + return nullptr; // TODO: cf bug 872568 + } + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "TextureHostShmemD3D11"; } #endif @@ -175,6 +180,11 @@ public: virtual bool Lock() MOZ_OVERRIDE; virtual void Unlock() MOZ_OVERRIDE; + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE + { + return nullptr; // TODO: cf bug 872568 + } + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "TextureHostDXGID3D11"; } #endif @@ -210,6 +220,11 @@ public: virtual bool IsYCbCrSource() const MOZ_OVERRIDE { return true; } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE + { + return nullptr; // TODO: cf bug 872568 + } + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() MOZ_OVERRIDE { return "TextureImageTextureHostD3D11"; } #endif diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp index fb06c98502a3..0ff1573a02d7 100644 --- a/gfx/layers/opengl/TextureHostOGL.cpp +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -141,7 +141,6 @@ TextureImageTextureHostOGL::GetSize() const return gfx::IntSize(0, 0); } - void TextureImageTextureHostOGL::SetCompositor(Compositor* aCompositor) { @@ -234,7 +233,7 @@ bool TextureImageTextureHostOGL::Lock() { if (!mTexture) { - NS_WARNING("TextureImageAsTextureHost to be composited without texture"); + NS_WARNING("TextureImageTextureHost to be composited without texture"); return false; } @@ -877,5 +876,67 @@ GrallocTextureHostOGL::SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* #endif +already_AddRefed +TextureImageTextureHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() ? + mGL->GetTexImage(mTexture->GetTextureID(), + false, + mTexture->GetShaderProgramType()) + : nullptr; + return surf.forget(); +} + +already_AddRefed +YCbCrTextureHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() ? + mGL->GetTexImage(mYTexture->mTexImage->GetTextureID(), + false, + mYTexture->mTexImage->GetShaderProgramType()) + : nullptr; + return surf.forget(); +} + +already_AddRefed +SharedTextureHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() ? + mGL->GetTexImage(GetTextureHandle(), + false, + GetShaderProgram()) + : nullptr; + return surf.forget(); +} + +already_AddRefed +SurfaceStreamHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() ? + mGL->GetTexImage(mTextureHandle, + false, + GetShaderProgram()) + : nullptr; + return surf.forget(); +} + +already_AddRefed +TiledTextureHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() ? + mGL->GetTexImage(mTextureHandle, + false, + GetShaderProgram()) + : nullptr; + return surf.forget(); +} + +#ifdef MOZ_WIDGET_GONK +already_AddRefed +GrallocTextureHostOGL::GetAsSurface() { + nsRefPtr surf = IsValid() && mGLTexture ? + mGL->GetTexImage(mGLTexture, + false, + GetShaderProgram()) + : nullptr; + return surf.forget(); +} +#endif + } // namespace } // namespace diff --git a/gfx/layers/opengl/TextureHostOGL.h b/gfx/layers/opengl/TextureHostOGL.h index 6eba1b575706..049f408238bd 100644 --- a/gfx/layers/opengl/TextureHostOGL.h +++ b/gfx/layers/opengl/TextureHostOGL.h @@ -131,6 +131,8 @@ public: virtual bool Lock() MOZ_OVERRIDE; + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + // textureSource void BindTexture(GLenum aTextureUnit) MOZ_OVERRIDE { @@ -272,7 +274,6 @@ public: { return mTexImage->GetWrapMode(); } - }; // TextureSource implementation @@ -296,6 +297,8 @@ public: return mYTexture->GetSize(); } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "YCbCrTextureHostOGL"; } #endif @@ -380,6 +383,8 @@ public: virtual gfx3DMatrix GetTextureTransform() MOZ_OVERRIDE; + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "SharedTextureHostOGL"; } #endif @@ -463,6 +468,8 @@ public: gfxASurface::CONTENT_COLOR; } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "SurfaceStreamHostOGL"; } #endif @@ -524,6 +531,8 @@ public: nsIntRegion* aRegion = nullptr) { MOZ_ASSERT(false, "Tiles should not use this path"); } + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "TiledTextureHostOGL"; } #endif @@ -607,6 +616,8 @@ public: bool IsValid() const MOZ_OVERRIDE; + virtual already_AddRefed GetAsSurface() MOZ_OVERRIDE; + #ifdef MOZ_LAYERS_HAVE_LOG virtual const char* Name() { return "GrallocTextureHostOGL"; } #endif diff --git a/gfx/thebes/gfxASurface.cpp b/gfx/thebes/gfxASurface.cpp index e632ef81d42b..f7d189b1e5db 100644 --- a/gfx/thebes/gfxASurface.cpp +++ b/gfx/thebes/gfxASurface.cpp @@ -704,7 +704,6 @@ gfxASurface::BytesPerPixel(gfxImageFormat aImageFormat) } } -#ifdef MOZ_DUMP_IMAGES void gfxASurface::WriteAsPNG(const char* aFile) { @@ -884,5 +883,3 @@ gfxASurface::WriteAsPNG_internal(FILE* aFile, bool aBinary) return; } -#endif - diff --git a/gfx/thebes/gfxASurface.h b/gfx/thebes/gfxASurface.h index 231404ebbf30..531e50a2a178 100644 --- a/gfx/thebes/gfxASurface.h +++ b/gfx/thebes/gfxASurface.h @@ -242,7 +242,6 @@ public: virtual const gfxIntSize GetSize() const { return gfxIntSize(-1, -1); } -#ifdef MOZ_DUMP_IMAGES /** * Debug functions to encode the current image as a PNG and export it. */ @@ -268,7 +267,6 @@ public: void CopyAsDataURL(); void WriteAsPNG_internal(FILE* aFile, bool aBinary); -#endif void SetOpaqueRect(const gfxRect& aRect) { if (aRect.IsEmpty()) { diff --git a/ipc/chromium/src/base/thread.cc b/ipc/chromium/src/base/thread.cc index 31ec62832ec3..4ae23f8ed074 100644 --- a/ipc/chromium/src/base/thread.cc +++ b/ipc/chromium/src/base/thread.cc @@ -137,7 +137,8 @@ void Thread::StopSoon() { } void Thread::ThreadMain() { - profiler_register_thread(name_.c_str()); + char aLocal; + profiler_register_thread(name_.c_str(), &aLocal); // The message loop for this thread. MessageLoop message_loop(startup_data_->options.message_loop_type); diff --git a/js/src/ion/BaselineCompiler.cpp b/js/src/ion/BaselineCompiler.cpp index 3b47c43fff42..800b566286cd 100644 --- a/js/src/ion/BaselineCompiler.cpp +++ b/js/src/ion/BaselineCompiler.cpp @@ -623,6 +623,13 @@ BaselineCompiler::emit_JSOP_POP() return true; } +bool +BaselineCompiler::emit_JSOP_POPN() +{ + frame.popn(GET_UINT16(pc)); + return true; +} + bool BaselineCompiler::emit_JSOP_DUP() { @@ -2392,6 +2399,17 @@ BaselineCompiler::emit_JSOP_SETRVAL() return true; } +bool +BaselineCompiler::emit_JSOP_CALLEE() +{ + JS_ASSERT(function()); + frame.syncStack(0); + masm.loadPtr(frame.addressOfCallee(), R0.scratchReg()); + masm.tagValue(JSVAL_TYPE_OBJECT, R0.scratchReg(), R0); + frame.push(R0); + return true; +} + bool BaselineCompiler::emit_JSOP_POPV() { diff --git a/js/src/ion/BaselineCompiler.h b/js/src/ion/BaselineCompiler.h index 6aa7cd9ed44a..b79ecc61e7ea 100644 --- a/js/src/ion/BaselineCompiler.h +++ b/js/src/ion/BaselineCompiler.h @@ -35,6 +35,7 @@ namespace ion { _(JSOP_LABEL) \ _(JSOP_NOTEARG) \ _(JSOP_POP) \ + _(JSOP_POPN) \ _(JSOP_DUP) \ _(JSOP_DUP2) \ _(JSOP_SWAP) \ @@ -159,6 +160,7 @@ namespace ion { _(JSOP_MOREITER) \ _(JSOP_ITERNEXT) \ _(JSOP_ENDITER) \ + _(JSOP_CALLEE) \ _(JSOP_POPV) \ _(JSOP_SETRVAL) \ _(JSOP_RETURN) \ diff --git a/js/src/ion/BaselineInspector.cpp b/js/src/ion/BaselineInspector.cpp index b47fb1631b91..4f15144cf449 100644 --- a/js/src/ion/BaselineInspector.cpp +++ b/js/src/ion/BaselineInspector.cpp @@ -40,6 +40,20 @@ SetElemICInspector::sawOOBTypedArrayWrite() const return false; } +bool +SetElemICInspector::sawDenseWrite() const +{ + if (!icEntry_) + return false; + + // Check for a SetElem_DenseAdd or SetElem_Dense stub. + for (ICStub *stub = icEntry_->firstStub(); stub; stub = stub->next()) { + if (stub->isSetElem_DenseAdd() || stub->isSetElem_Dense()) + return true; + } + return false; +} + bool BaselineInspector::maybeShapesForPropertyOp(jsbytecode *pc, Vector &shapes) { diff --git a/js/src/ion/BaselineInspector.h b/js/src/ion/BaselineInspector.h index a078e3ad4e5d..d6ddc4650b37 100644 --- a/js/src/ion/BaselineInspector.h +++ b/js/src/ion/BaselineInspector.h @@ -41,6 +41,7 @@ class SetElemICInspector : public ICInspector bool sawOOBDenseWrite() const; bool sawOOBTypedArrayWrite() const; + bool sawDenseWrite() const; }; class BaselineInspector diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 83a0e2bd38e3..f8f8c445d8e6 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -1244,6 +1244,11 @@ IonBuilder::inspectOpcode(JSOp op) return true; return maybeInsertResume(); + case JSOP_POPN: + for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) + current->pop(); + return true; + case JSOP_NEWINIT: { if (GET_UINT8(pc) == JSProto_Array) @@ -6615,6 +6620,13 @@ IonBuilder::jsop_setelem() break; } + // TODO: Bug 876650: remove this check: + // Temporary disable the cache if non dense native, + // untill the cache supports more ics + SetElemICInspector icInspect(inspector->setElemICInspector(pc)); + if (!icInspect.sawDenseWrite()) + break; + MInstruction *ins = MSetElementCache::New(object, index, value, script()->strict); current->add(ins); current->push(value); @@ -6845,7 +6857,9 @@ IonBuilder::jsop_length_fastPath() MDefinition *obj = current->peek(-1); - if (obj->type() == MIRType_String) { + if (obj->mightBeType(MIRType_String)) { + if (obj->mightBeType(MIRType_Object)) + return false; current->pop(); MStringLength *ins = MStringLength::New(obj); current->add(ins); @@ -6853,7 +6867,7 @@ IonBuilder::jsop_length_fastPath() return true; } - if (obj->type() == MIRType_Object) { + if (obj->mightBeType(MIRType_Object)) { types::StackTypeSet *objTypes = obj->resultTypeSet(); if (objTypes && diff --git a/js/src/ion/Lowering.cpp b/js/src/ion/Lowering.cpp index 1bc8349e667c..82a951593075 100644 --- a/js/src/ion/Lowering.cpp +++ b/js/src/ion/Lowering.cpp @@ -857,8 +857,8 @@ ReorderCommutative(MDefinition **lhsp, MDefinition **rhsp) // Ensure that if there is a constant, then it is in rhs. // In addition, since clobbering binary operations clobber the left - // operand, prefer a lhs operand with no further uses. - if (lhs->isConstant() || rhs->useCount() == 1) { + // operand, prefer a non-constant lhs operand with no further uses. + if (lhs->isConstant() || (!rhs->isConstant() && rhs->useCount() == 1)) { *rhsp = lhs; *lhsp = rhs; } diff --git a/js/src/ion/TypePolicy.cpp b/js/src/ion/TypePolicy.cpp index 79cedf74350c..9d2e3ff11f47 100644 --- a/js/src/ion/TypePolicy.cpp +++ b/js/src/ion/TypePolicy.cpp @@ -191,6 +191,8 @@ ComparePolicy::adjustInputs(MInstruction *def) convert = MToDouble::NonNullNonStringPrimitives; else if (compare->compareType() == MCompare::Compare_DoubleMaybeCoerceRHS && i == 1) convert = MToDouble::NonNullNonStringPrimitives; + if (convert == MToDouble::NumbersOnly && in->type() == MIRType_Boolean) + in = boxAt(def, in); replace = MToDouble::New(in, convert); break; } diff --git a/js/src/jit-test/tests/baseline/callee.js b/js/src/jit-test/tests/baseline/callee.js new file mode 100644 index 000000000000..328574dcc9d6 --- /dev/null +++ b/js/src/jit-test/tests/baseline/callee.js @@ -0,0 +1,5 @@ +ff = (function g() { + for (var i=0; i<15; i++) {} + return g; +}); +assertEq(ff(), ff); diff --git a/js/src/jit-test/tests/basic/bug522136.js b/js/src/jit-test/tests/basic/bug522136.js index 8a86f7fc3778..751396dbd546 100644 --- a/js/src/jit-test/tests/basic/bug522136.js +++ b/js/src/jit-test/tests/basic/bug522136.js @@ -7,4 +7,4 @@ try { } // Exact behavior of recursion check depends on which JIT we use. -assertEq(thrown && Q > 8000, true); +assertEq(thrown && Q > 3500, true); diff --git a/js/src/jit-test/tests/basic/bug876226.js b/js/src/jit-test/tests/basic/bug876226.js new file mode 100644 index 000000000000..e90ae777505a --- /dev/null +++ b/js/src/jit-test/tests/basic/bug876226.js @@ -0,0 +1,11 @@ +// |jit-test| error: SyntaxError +try { + evaluate("throw 3", { + newContext: new Set, + saveFrameChain: true + }); +} catch(e) {} + +evaluate("()", { + saveFrameChain: true +}); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-01.js b/js/src/jit-test/tests/debug/Script-sourceStart-01.js new file mode 100644 index 000000000000..99764b80f3f3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-01.js @@ -0,0 +1,22 @@ +/* + * Script.prototype.sourceStart and Script.prototype.sourceLength should both be + * a number. + */ +let g = newGlobal('new-compartment'); +let dbg = new Debugger(g); + +var count = 0; +function test(string, range) { + dbg.onNewScript = function (script) { + ++count; + assertEq(script.sourceStart, range[0]); + assertEq(script.sourceLength, range[1]); + }; + + g.eval(string); +}; + +test("", [0, 0]); +test("2 * 3", [0, 5]); +test("2\n*\n3", [0, 5]); +assertEq(count, 3); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-02.js b/js/src/jit-test/tests/debug/Script-sourceStart-02.js new file mode 100644 index 000000000000..1aa11b6bf484 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-02.js @@ -0,0 +1,32 @@ +/* + * For function statements, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise both the opening '(' and the + * closing '}'. + */ +let g = newGlobal('new-compartment'); +let dbg = new Debugger(g); + +function test(string, ranges) { + var index = 0; + dbg.onNewScript = function (script) { + function traverse(script) { + script.getChildScripts().forEach(function (script) { + assertEq(script.sourceStart, ranges[index][0]); + assertEq(script.sourceLength, ranges[index][1]); + ++index; + traverse(script); + }); + } + traverse(script); + }; + + g.eval(string); + assertEq(index, ranges.length); +}; + +test("function f() {}", [[10, 5]]); +test("function f() { function g() {} }", [[10, 22], [25, 5]]); +test("function f() { function g() { function h() {} } }", [[10, 39], [25, 22], [40, 5]]); +test("function f() { if (true) function g() {} }", [[10, 32], [35, 5]]); +test("var o = { get p () {} }", [[16, 5]]); +test("var o = { set p (x) {} }", [[16, 6]]); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-03.js b/js/src/jit-test/tests/debug/Script-sourceStart-03.js new file mode 100644 index 000000000000..b4a63e615a15 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-03.js @@ -0,0 +1,35 @@ +/* + * For arrow functions, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise the entire function expression + * (including arguments) + */ +let g = newGlobal('new-compartment'); +let dbg = new Debugger(g); + +function test(string, ranges) { + var index = 0; + dbg.onNewScript = function (script) { + function traverse(script) { + script.getChildScripts().forEach(function (script) { + assertEq(script.sourceStart, ranges[index][0]); + assertEq(script.sourceLength, ranges[index][1]); + ++index; + traverse(script); + }); + } + traverse(script); + }; + + g.eval(string); + + /* + * In some configurations certain child scripts are listed twice, so we + * cannot rely on index always having the exact same value + */ + assertEq(0 < index && index <= ranges.length, true); +}; + +test("() => {}", [[0, 8]]); +test("(x, y) => { x * y }", [[0, 19]]); +test("x => x * x", [[0, 10]]); +test("x => x => x * x", [[0, 15], [5, 10], [5, 10]]); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-04.js b/js/src/jit-test/tests/debug/Script-sourceStart-04.js new file mode 100644 index 000000000000..1f93b22a75f7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-04.js @@ -0,0 +1,25 @@ +/* + * For eval and Function constructors, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise the entire script (excluding + * arguments in the case of Function constructors) + */ +let g = newGlobal('new-compartment'); +let dbg = new Debugger(g); + +var count = 0; +function test(string, range) { + dbg.onNewScript = function (script) { + ++count; + if (count % 2 == 0) { + assertEq(script.sourceStart, range[0]); + assertEq(script.sourceLength, range[1]); + } + } + + g.eval(string); +} + +test("eval('2 * 3')", [0, 5]); +test("new Function('2 * 3')", [0, 5]); +test("new Function('x', 'x * x')", [0, 5]); +assertEq(count, 6); diff --git a/js/src/jit-test/tests/debug/Source-url.js b/js/src/jit-test/tests/debug/Source-url.js new file mode 100644 index 000000000000..52bfb1bfb39f --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-url.js @@ -0,0 +1,10 @@ +// Source.prototype.url can be a string or null. + +var g = newGlobal('new-compartment'); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +for (var fileName of ['file:///var/foo.js', null]) { + g.evaluate("function f(x) { return 2*x; }", {fileName: fileName}); + var fw = gw.getOwnPropertyDescriptor('f').value; + assertEq(fw.script.source.url, fileName); +} diff --git a/js/src/jit-test/tests/ion/bug867820.js b/js/src/jit-test/tests/ion/bug867820.js new file mode 100644 index 000000000000..2ee5c632340d --- /dev/null +++ b/js/src/jit-test/tests/ion/bug867820.js @@ -0,0 +1,18 @@ + +function AddTestCase(expect, actual) { + new TestCase(expect, actual); +} +function TestCase(e, a) { + this.expect = e; + getTestCaseResult(e, a); +} +function getTestCaseResult(expected, actual) { + if (actual != expected) {} +} +AddRegExpCases(false, Math.pow(2,31)); +AddRegExpCases("", Math.pow(2,30) - 1); +function AddRegExpCases(m, l) { + AddTestCase(""); + AddTestCase(m, true); + AddTestCase(l, 0); +} diff --git a/js/src/jit-test/tests/ion/popn.js b/js/src/jit-test/tests/ion/popn.js new file mode 100644 index 000000000000..06e2f5fb85d2 --- /dev/null +++ b/js/src/jit-test/tests/ion/popn.js @@ -0,0 +1,11 @@ +function f() { + var t = 0; + for (var j = 0; j < 10; j++) { + for (var i = 0; i < 9; ++i) { + var [r, g, b] = [1, i, -10]; + t += r + g + b; + } + } + return t; +} +assertEq(f(), -450); diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 2403f91923db..049b68b45961 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -26,6 +26,7 @@ CPP_SOURCES += [ 'testDefineProperty.cpp', 'testEnclosingFunction.cpp', 'testErrorCopying.cpp', + 'testException.cpp', 'testExternalStrings.cpp', 'testFindSCCs.cpp', 'testFuncCallback.cpp', diff --git a/js/src/jsapi-tests/testException.cpp b/js/src/jsapi-tests/testException.cpp new file mode 100644 index 000000000000..b5965f546234 --- /dev/null +++ b/js/src/jsapi-tests/testException.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + */ +/* 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/. */ + + +#include "tests.h" + +BEGIN_TEST(testException_bug860435) +{ + JS::RootedValue fun(cx); + + EVAL("ReferenceError", fun.address()); + CHECK(fun.isObject()); + + JS::RootedValue v(cx); + JS_CallFunctionValue(cx, global, fun, 0, v.address(), v.address()); + CHECK(v.isObject()); + + JS_GetProperty(cx, &v.toObject(), "stack", v.address()); + CHECK(v.isString()); + return true; +} +END_TEST(testException_bug860435) diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index b01208b32192..9eb9e5d96ccc 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -571,7 +571,7 @@ Exception(JSContext *cx, unsigned argc, Value *vp) NonBuiltinScriptFrameIter iter(cx); /* Set the 'fileName' property. */ - RootedScript script(cx, iter.script()); + RootedScript script(cx, iter.done() ? NULL : iter.script()); RootedString filename(cx); if (args.length() > 1) { filename = ToString(cx, args[1]); diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 226c4b4ac9d4..0cfb869cedc7 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -3197,7 +3197,7 @@ JS_FRIEND_DATA(Class) js::FunctionProxyClass = { proxy_Finalize, /* finalize */ NULL, /* checkAccess */ proxy_Call, - FunctionClass.hasInstance, + proxy_HasInstance, proxy_Construct, proxy_TraceFunction, /* trace */ PROXY_CLASS_EXT, diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 1d3227c5f32d..4937b08ae1db 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -883,6 +883,30 @@ class AutoNewContext } }; +class AutoSaveFrameChain +{ + JSContext *cx_; + bool saved_; + + public: + AutoSaveFrameChain(JSContext *cx) + : cx_(cx), + saved_(false) + {} + + bool save() { + if (!JS_SaveFrameChain(cx_)) + return false; + saved_ = true; + return true; + } + + ~AutoSaveFrameChain() { + if (saved_) + JS_RestoreFrameChain(cx_); + } +}; + static JSBool Evaluate(JSContext *cx, unsigned argc, jsval *vp) { @@ -1025,7 +1049,8 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp) cx = ancx.get(); } - if (saveFrameChain && !JS_SaveFrameChain(cx)) + AutoSaveFrameChain asfc(cx); + if (saveFrameChain && !asfc.save()) return false; { @@ -1064,9 +1089,6 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp) } } - if (saveFrameChain) - JS_RestoreFrameChain(cx); - return JS_WrapValue(cx, vp); } @@ -3603,6 +3625,8 @@ static JSFunctionSpecWithHelp shell_functions[] = { " lineNumber: starting line number for error messages and debug info\n" " global: global in which to execute the code\n" " newContext: if true, create and use a new cx (default: false)\n" +" saveFrameChain: if true, save the frame chain before evaluating code\n" +" and restore it afterwards\n" " catchTermination: if true, catch termination (failure without\n" " an exception value, as for slow scripts or out-of-memory)\n" " and return 'terminated'\n"), diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index d75787361ad2..dcfe192546a1 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -2848,6 +2848,22 @@ DebuggerScript_getSource(JSContext *cx, unsigned argc, Value *vp) return true; } +static JSBool +DebuggerScript_getSourceStart(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceStart)", args, obj, script); + args.rval().setNumber(script->sourceStart); + return true; +} + +static JSBool +DebuggerScript_getSourceLength(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script); + args.rval().setNumber(script->sourceEnd - script->sourceStart); + return true; +} + static JSBool DebuggerScript_getStaticLevel(JSContext *cx, unsigned argc, Value *vp) { @@ -3485,6 +3501,8 @@ static const JSPropertySpec DebuggerScript_properties[] = { JS_PSG("startLine", DebuggerScript_getStartLine, 0), JS_PSG("lineCount", DebuggerScript_getLineCount, 0), JS_PSG("source", DebuggerScript_getSource, 0), + JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0), + JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0), JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0), JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0), JS_PS_END @@ -3638,8 +3656,26 @@ DebuggerSource_getText(JSContext *cx, unsigned argc, Value *vp) return true; } +static JSBool +DebuggerSource_getUrl(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject); + + ScriptSource *ss = sourceObject->source(); + if (ss->filename()) { + JSString *str = js_NewStringCopyZ(cx, ss->filename()); + if (!str) + return false; + args.rval().setString(str); + } else { + args.rval().setNull(); + } + return true; +} + static const JSPropertySpec DebuggerSource_properties[] = { JS_PSG("text", DebuggerSource_getText, 0), + JS_PSG("url", DebuggerSource_getUrl, 0), JS_PS_END }; diff --git a/js/xpconnect/idl/nsIScriptError.idl b/js/xpconnect/idl/nsIScriptError.idl index 74388de4a495..b4ffd8b55be2 100644 --- a/js/xpconnect/idl/nsIScriptError.idl +++ b/js/xpconnect/idl/nsIScriptError.idl @@ -11,7 +11,7 @@ #include "nsISupports.idl" #include "nsIConsoleMessage.idl" -[scriptable, uuid(ec640482-be5f-49a0-a9cb-c87eacce9291)] +[scriptable, uuid(cac9d8e8-0d53-4fa8-9903-bb367e4fa1fe)] interface nsIScriptError : nsIConsoleMessage { /** pseudo-flag for default case */ @@ -49,15 +49,6 @@ interface nsIScriptError : nsIConsoleMessage */ readonly attribute string category; - /* - The time (in milliseconds from the Epoch) that the script error instance - was initialised, and thus the time when the error occurred. - Currently used to display date and time of the message in Error console. - The timestamp is initialized as JS_now/1000 so that it can be - compared to Date.now in Javascript. - */ - readonly attribute long long timeStamp; - /* Get the window id this was initialized with. Zero will be returned if init() was used instead of initWithWindowID(). */ readonly attribute unsigned long long outerWindowID; @@ -87,13 +78,11 @@ interface nsIScriptError : nsIConsoleMessage in uint32_t flags, in string category, in unsigned long long innerWindowID); - - AUTF8String toString(); }; %{ C++ #define NS_SCRIPTERROR_CID \ -{ 0xe38e53b9, 0x5bb0, 0x456a, { 0xb5, 0x53, 0x57, 0x93, 0x70, 0xcb, 0x15, 0x67 }} +{ 0x1950539a, 0x90f0, 0x4d22, { 0xb5, 0xaf, 0x71, 0x32, 0x9c, 0x68, 0xfa, 0x35 }} #define NS_SCRIPTERROR_CONTRACTID "@mozilla.org/scripterror;1" %} diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp index a5da72ffdf47..675388f67930 100644 --- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -23,7 +23,6 @@ #include "nsJSEnvironment.h" #include "nsThreadUtils.h" #include "nsDOMJSUtils.h" -#include "nsContentUtils.h" #include "XrayWrapper.h" #include "WrapperFactory.h" diff --git a/js/xpconnect/tests/mochitest/Makefile.in b/js/xpconnect/tests/mochitest/Makefile.in index cfa3b9a91f2e..4c947e94803a 100644 --- a/js/xpconnect/tests/mochitest/Makefile.in +++ b/js/xpconnect/tests/mochitest/Makefile.in @@ -94,6 +94,7 @@ MOCHITEST_FILES = chrome_wrappers_helper.html \ test_bug829872.html \ test_bug862380.html \ test_bug865260.html \ + test_bug870423.html \ test_bug871887.html \ file_crosscompartment_weakmap.html \ test_crosscompartment_weakmap.html \ diff --git a/js/xpconnect/tests/mochitest/test_bug870423.html b/js/xpconnect/tests/mochitest/test_bug870423.html new file mode 100644 index 000000000000..b951585b602c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug870423.html @@ -0,0 +1,51 @@ + + + + + + Test for Bug 870423 + + + + + +Mozilla Bug 870423 +

+ + + +
+
+ + diff --git a/layout/forms/nsComboboxControlFrame.cpp b/layout/forms/nsComboboxControlFrame.cpp index 3edd8c98aee4..81c53dacd3f4 100644 --- a/layout/forms/nsComboboxControlFrame.cpp +++ b/layout/forms/nsComboboxControlFrame.cpp @@ -131,26 +131,6 @@ NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, u NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame) -namespace { - -class DestroyWidgetRunnable : public nsRunnable { -public: - NS_DECL_NSIRUNNABLE - - explicit DestroyWidgetRunnable(nsIWidget* aWidget) : mWidget(aWidget) {} - -private: - nsCOMPtr mWidget; -}; - -NS_IMETHODIMP DestroyWidgetRunnable::Run() -{ - mWidget = nullptr; - return NS_OK; -} - -} - //----------------------------------------------------------- // Reflow Debugging Macros // These let us "see" how many reflow counts are happening @@ -389,12 +369,7 @@ nsComboboxControlFrame::ShowList(bool aShowList) } } else { if (widget) { - nsCOMPtr widgetDestroyer = - new DestroyWidgetRunnable(widget); - // 'widgetDestroyer' now has a strong ref on the widget so calling - // DestroyWidget here will not *delete* it. view->DestroyWidget(); - NS_DispatchToMainThread(widgetDestroyer); } } diff --git a/layout/generic/crashtests/876074-1.html b/layout/generic/crashtests/876074-1.html new file mode 100644 index 000000000000..fb394480dde8 --- /dev/null +++ b/layout/generic/crashtests/876074-1.html @@ -0,0 +1,17 @@ + + + + + + + +
+ + diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list index 9dfb8e4e25c7..456684f1f275 100644 --- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -496,3 +496,4 @@ test-pref(layout.css.flexbox.enabled,true) load 854263-1.html test-pref(layout.css.flexbox.enabled,true) load 862947-1.html needs-focus pref(accessibility.browsewithcaret,true) load 868906.html test-pref(layout.css.flexbox.enabled,true) load 866547-1.html +asserts(1-4) test-pref(layout.css.flexbox.enabled,true) load 876074-1.html # bug 876749 diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index b87287b7d60b..92375b6d7e3a 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1138,8 +1138,9 @@ nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { - MOZ_ASSERT(nsLayoutUtils::IsFrameListSorted(mFrames), - "Frame list should've been sorted in reflow"); + NS_ASSERTION( + nsLayoutUtils::IsFrameListSorted(mFrames), + "Child frames aren't sorted correctly"); DisplayBorderBackgroundOutline(aBuilder, aLists); diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index f6333dc11573..74446c6b7c24 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -3768,7 +3768,9 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState, AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false); } - AdjustOverlappingScrollbars(vRect, hRect); + if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) { + AdjustOverlappingScrollbars(vRect, hRect); + } if (mVScrollbarBox) { nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect); } diff --git a/layout/generic/nsPlaceholderFrame.cpp b/layout/generic/nsPlaceholderFrame.cpp index 50c8ee2cda52..59cef88a0863 100644 --- a/layout/generic/nsPlaceholderFrame.cpp +++ b/layout/generic/nsPlaceholderFrame.cpp @@ -97,6 +97,38 @@ nsPlaceholderFrame::Reflow(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { +#ifdef DEBUG + // We should be getting reflowed before our out-of-flow. + // If this is our first reflow, and our out-of-flow has already received its + // first reflow (before us), complain. + // XXXdholbert This "look for a previous continuation or IB-split sibling" + // code could use nsLayoutUtils::GetPrevContinuationOrSpecialSibling(), if + // we ever add a function like that. (We currently have a "Next" version.) + if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && + !(mOutOfFlowFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + + // Unfortunately, this can currently happen when the placeholder is in a + // later continuation or later IB-split sibling than its out-of-flow (as + // is the case in some of our existing unit tests). So for now, in that + // case, we'll warn instead of asserting. + bool isInContinuationOrIBSplit = false; + nsIFrame* ancestor = this; + while ((ancestor = ancestor->GetParent())) { + if (ancestor->GetPrevContinuation() || + ancestor->Properties().Get(IBSplitSpecialPrevSibling())) { + isInContinuationOrIBSplit = true; + break; + } + } + + if (isInContinuationOrIBSplit) { + NS_WARNING("Out-of-flow frame got reflowed before its placeholder"); + } else { + NS_ERROR("Out-of-flow frame got reflowed before its placeholder"); + } + } +#endif + DO_GLOBAL_REFLOW_COUNT("nsPlaceholderFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); aDesiredSize.width = 0; diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp index 63e26b4368ad..9bf807d85094 100644 --- a/layout/generic/nsVideoFrame.cpp +++ b/layout/generic/nsVideoFrame.cpp @@ -234,6 +234,20 @@ nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder, return result.forget(); } +class DispatchResizeToControls : public nsRunnable +{ +public: + DispatchResizeToControls(nsIContent* aContent) + : mContent(aContent) {} + NS_IMETHOD Run() { + nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, + NS_LITERAL_STRING("resizevideocontrols"), + false, false); + return NS_OK; + } + nsCOMPtr mContent; +}; + NS_IMETHODIMP nsVideoFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aMetrics, @@ -307,12 +321,17 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext, } else if (child->GetContent() == mVideoControls) { // Reflow the video controls frame. nsBoxLayoutState boxState(PresContext(), aReflowState.rendContext); + nsSize size = child->GetSize(); nsBoxFrame::LayoutChildAt(boxState, child, nsRect(mBorderPadding.left, mBorderPadding.top, aReflowState.ComputedWidth(), aReflowState.ComputedHeight())); + if (child->GetSize() != size) { + nsRefPtr event = new DispatchResizeToControls(child->GetContent()); + nsContentUtils::AddScriptRunner(event); + } } else if (child->GetContent() == mCaptionDiv) { // Reflow to caption div nsHTMLReflowMetrics kidDesiredSize; diff --git a/layout/mathml/nsMathMLSelectedFrame.h b/layout/mathml/nsMathMLSelectedFrame.h index b08bcb28d43c..2dce478550b8 100644 --- a/layout/mathml/nsMathMLSelectedFrame.h +++ b/layout/mathml/nsMathMLSelectedFrame.h @@ -40,6 +40,8 @@ public: const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) MOZ_OVERRIDE; + virtual nsQueryFrame::FrameIID GetFrameId() = 0; + protected: nsMathMLSelectedFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {} @@ -49,6 +51,9 @@ protected: nsIFrame* mSelectedFrame; bool mInvalidMarkup; + +private: + void* operator new(size_t, nsIPresShell*) MOZ_MUST_OVERRIDE MOZ_DELETE; }; #endif /* nsMathMLSelectedFrame_h___ */ diff --git a/layout/reftests/css-animations/print-no-animations-notref.html b/layout/reftests/css-animations/print-no-animations-notref.html new file mode 100644 index 000000000000..dadf405ea861 --- /dev/null +++ b/layout/reftests/css-animations/print-no-animations-notref.html @@ -0,0 +1,11 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/css-animations/print-no-animations-ref.html b/layout/reftests/css-animations/print-no-animations-ref.html new file mode 100644 index 000000000000..f4d3dde3654a --- /dev/null +++ b/layout/reftests/css-animations/print-no-animations-ref.html @@ -0,0 +1,11 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/css-animations/print-no-animations.html b/layout/reftests/css-animations/print-no-animations.html new file mode 100644 index 000000000000..a52334cd8628 --- /dev/null +++ b/layout/reftests/css-animations/print-no-animations.html @@ -0,0 +1,16 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/css-animations/reftest.list b/layout/reftests/css-animations/reftest.list new file mode 100644 index 000000000000..05a38ffc91c3 --- /dev/null +++ b/layout/reftests/css-animations/reftest.list @@ -0,0 +1,4 @@ +== screen-animations.html screen-animations-ref.html +!= screen-animations.html screen-animations-notref.html +fails == print-no-animations.html print-no-animations-ref.html # reftest harness doesn't actually make pres context non-dynamic for reftest-print tests +fails != print-no-animations.html print-no-animations-notref.html # reftest harness doesn't actually make pres context non-dynamic for reftest-print tests diff --git a/layout/reftests/css-animations/screen-animations-notref.html b/layout/reftests/css-animations/screen-animations-notref.html new file mode 100644 index 000000000000..73a026f6033b --- /dev/null +++ b/layout/reftests/css-animations/screen-animations-notref.html @@ -0,0 +1,11 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/css-animations/screen-animations-ref.html b/layout/reftests/css-animations/screen-animations-ref.html new file mode 100644 index 000000000000..8830913084c3 --- /dev/null +++ b/layout/reftests/css-animations/screen-animations-ref.html @@ -0,0 +1,11 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/css-animations/screen-animations.html b/layout/reftests/css-animations/screen-animations.html new file mode 100644 index 000000000000..ba1a0aa14ecc --- /dev/null +++ b/layout/reftests/css-animations/screen-animations.html @@ -0,0 +1,16 @@ + + +Static CSS animation + +

blue with animation support; olive without

diff --git a/layout/reftests/reftest.list b/layout/reftests/reftest.list index 8d41b32e4ea5..769499cd0734 100644 --- a/layout/reftests/reftest.list +++ b/layout/reftests/reftest.list @@ -48,6 +48,9 @@ include bugs/reftest.list # canvas 2D include canvas/reftest.list +# css animations +include css-animations/reftest.list + # css calc() tests include css-calc/reftest.list diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp index fd5f2d5efa0f..55d677853667 100644 --- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -539,6 +539,11 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { if (!mPresContext->IsProcessingAnimationStyleChange()) { + if (!mPresContext->IsDynamic()) { + // For print or print preview, ignore animations. + return nullptr; + } + // Everything that causes our animation data to change triggers a // style change, which in turn triggers a non-animation restyle. // Likewise, when we initially construct frames, we're not in a @@ -949,6 +954,11 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, aPseudoType == nsCSSPseudoElements::ePseudo_after, "forbidden pseudo type"); + if (!mPresContext->IsDynamic()) { + // For print or print preview, ignore animations. + return nullptr; + } + ElementAnimations *ea = GetElementAnimations(aElement, aPseudoType, false); if (!ea) { diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index fca3adec952b..92f5b674b39c 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -4177,6 +4177,7 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector, if (eCSSToken_Number != mToken.mType || !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) { REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + UngetToken(); return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') } numbers[1] = mToken.mInteger * sign[1]; diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp index 06d14882ae77..628d75ebc4d4 100644 --- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -451,6 +451,11 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement, aNewStyleContext->HasPseudoElementData(), "pseudo type mismatch"); + if (!mPresContext->IsDynamic()) { + // For print or print preview, ignore transitions. + return nullptr; + } + // NOTE: Things in this function (and ConsiderStartingTransition) // should never call PeekStyleData because we don't preserve gotten // structs across reframes. @@ -893,6 +898,11 @@ nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData return; } + if (!mPresContext->IsDynamic()) { + // For print or print preview, ignore animations. + return; + } + if (aData->mPresContext->IsProcessingRestyles() && !aData->mPresContext->IsProcessingAnimationStyleChange()) { // If we're processing a normal style change rather than one from diff --git a/layout/style/test/test_selectors.html b/layout/style/test/test_selectors.html index 6c20d742eeb4..43d5fa876e73 100644 --- a/layout/style/test/test_selectors.html +++ b/layout/style/test/test_selectors.html @@ -628,6 +628,19 @@ function run() { test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )"); test_parseable(":nth-child(+1/**/n-1)"); test_parseable(":nth-child(1/**/n-1)"); + // bug 876570 + test_balanced_unparseable(":nth-child(+2n-)"); + test_balanced_unparseable(":nth-child(+n-)"); + test_balanced_unparseable(":nth-child(-2n-)"); + test_balanced_unparseable(":nth-child(-n-)"); + test_balanced_unparseable(":nth-child(2n-)"); + test_balanced_unparseable(":nth-child(n-)"); + test_balanced_unparseable(":nth-child(+2n+)"); + test_balanced_unparseable(":nth-child(+n+)"); + test_balanced_unparseable(":nth-child(-2n+)"); + test_balanced_unparseable(":nth-child(-n+)"); + test_balanced_unparseable(":nth-child(2n+)"); + test_balanced_unparseable(":nth-child(n+)"); // exercise the an+b matching logic particularly hard for // :nth-child() (since we know we use the same code for all 4) diff --git a/layout/tools/reftest/reftest.js b/layout/tools/reftest/reftest.js index 17ad0e55e1c0..904eba81c707 100644 --- a/layout/tools/reftest/reftest.js +++ b/layout/tools/reftest/reftest.js @@ -1504,7 +1504,7 @@ function RecordResult(testRunTime, errorMsg, scriptResults) gCurrentCanvas = gURICanvases[gCurrentURL]; } if (gCurrentCanvas == null) { - gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | program error managing snapshots\n"); + gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | program error managing snapshots\n"); ++gTestResults.Exception; } if (gState == 1) { @@ -1817,7 +1817,7 @@ function RecvContentReady() function RecvException(what) { - gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | "+ what +"\n"); + gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | " + what + "\n"); ++gTestResults.Exception; } @@ -1840,7 +1840,7 @@ function RecvLog(type, msg) } else if (type == "warning") { LogWarning(msg); } else { - gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | unknown log type "+ type +"\n"); + gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | unknown log type " + type + "\n"); ++gTestResults.Exception; } } diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h index 805ac087cbf4..40dd539caaf5 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h @@ -205,10 +205,10 @@ class RemoteSourceStreamInfo { RemoteSourceStreamInfo(already_AddRefed aMediaStream, PeerConnectionMedia *aParent) - : mMediaStream(aMediaStream), + : mTrackTypeHints(0), + mMediaStream(aMediaStream), mPipelines(), - mParent(aParent), - mTrackTypeHints(0) { + mParent(aParent) { MOZ_ASSERT(mMediaStream); } diff --git a/media/webrtc/signaling/src/sipcc/core/ccapp/ccprovider.c b/media/webrtc/signaling/src/sipcc/core/ccapp/ccprovider.c index 295d573f4eaf..924a7f1cc1c5 100755 --- a/media/webrtc/signaling/src/sipcc/core/ccapp/ccprovider.c +++ b/media/webrtc/signaling/src/sipcc/core/ccapp/ccprovider.c @@ -581,9 +581,9 @@ static void updateVideoPref( unsigned int event, line_t line_id, callid_t call_i * digits - memory to return the first param * Returns: */ -void getDigits(string_t data, char *digits) { +static void getDigits(string_t data, char *digits, unsigned int buffer_length) { char *endptr; - int len=0; + unsigned int len=0; digits[0]=0; @@ -595,6 +595,11 @@ void getDigits(string_t data, char *digits) { len = strlen(data); } + /* prevent len from writing past buffer size */ + if (len >= buffer_length) { + len = buffer_length - 1; + } + if ( len) { memcpy(digits, data, len); digits[len] = 0; @@ -692,7 +697,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d break; case CC_FEATURE_DIALSTR: if (CheckAndGetAvailableLine(&line_id, &call_id) == TRUE) { - getDigits(data, digits); + getDigits(data, digits, sizeof(digits)); if (strlen(digits) == 0) { //if dial string is empty then go offhook cc_offhook(CC_SRC_UI, call_id, line_id); @@ -779,7 +784,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d break; } - getDigits(data,digits); + getDigits(data, digits, sizeof(digits)); dp_int_init_dialing_data(line_id, call_id); dp_int_dial_immediate(line_id, call_id, TRUE, @@ -890,7 +895,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d }// DON'T ADD BREAK HERE. EVENT IS PASSED BELOW case CC_FEATURE_B2BCONF: case CC_FEATURE_XFER: - getDigits(data,digits); + getDigits(data, digits, sizeof(digits)); if ( strlen(digits)) { cc_feature_data_t ftr_data; CCAPP_DEBUG(DEB_F_PREFIX"conf: sid=%s.", DEB_F_PREFIX_ARGS(SIP_CC_PROV, fname),data); diff --git a/mfbt/Atomics.h b/mfbt/Atomics.h index d79af6c6952a..d705b06ca9fb 100644 --- a/mfbt/Atomics.h +++ b/mfbt/Atomics.h @@ -620,7 +620,7 @@ struct PrimitiveIntrinsics<8> static Type exchange(Type* ptr, Type val) { return _InterlockedExchange64(ptr, val); } - static bool compareExchange(T* ptr, T oldVal, T newVal) { + static bool compareExchange(Type* ptr, Type oldVal, Type newVal) { return _InterlockedCompareExchange64(ptr, newVal, oldVal) == oldVal; } }; diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index ebcc62218016..3f27765e75ae 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -391,8 +391,8 @@ * error to use it, or an array of such objects, as the type of a new * expression (unless placement new is being used). If a member of another * class uses this class, or if another class inherits from this class, then - * it is considered to be a stack class as well, although this attribute need - * not be provided in such cases. + * it is considered to be a non-heap class as well, although this attribute + * need not be provided in such cases. */ #ifdef MOZ_CLANG_PLUGIN # define MOZ_MUST_OVERRIDE __attribute__((annotate("moz_must_override"))) diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 318c495e73d1..478164dccef4 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -95,6 +95,9 @@ pref("network.http.max-connections", 20); pref("network.http.max-persistent-connections-per-server", 6); pref("network.http.max-persistent-connections-per-proxy", 20); +// spdy +pref("network.http.spdy.push-allowance", 32768); + // See bug 545869 for details on why these are set the way they are pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 16384); @@ -357,11 +360,6 @@ pref("gfx.displayport.strategy_vb.danger_y_incr", -1); // additional danger zone // prediction bias strategy options pref("gfx.displayport.strategy_pb.threshold", -1); // velocity threshold in inches/frame -// disable Graphite font shaping by default on Android until memory footprint -// of using the Charis SIL fonts that we ship with the product is addressed -// (see bug 700023, bug 846832, bug 847344) -pref("gfx.font_rendering.graphite.enabled", false); - // don't allow JS to move and resize existing windows pref("dom.disable_window_move_resize", true); diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index be39922e02ba..704cc56260dc 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -464,10 +464,8 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, updateTitle(); } break; - case RESTORED: - updateTabCount(Tabs.getInstance().getDisplayCount()); - break; case SELECTED: + updateTabCount(Tabs.getInstance().getDisplayCount()); mSwitchingTabs = true; // fall through case LOCATION_CHANGE: @@ -479,7 +477,7 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, break; case CLOSED: case ADDED: - updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); + updateTabCount(Tabs.getInstance().getDisplayCount()); if (Tabs.getInstance().isSelectedTab(tab)) { updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); @@ -647,7 +645,7 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, // Trigger animation to update the tabs counter once the // tabs button is back on screen. - updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); + updateTabCount(Tabs.getInstance().getDisplayCount()); } }); @@ -748,26 +746,6 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, } } - public void updateTabCountAndAnimate(int count) { - // Don't animate if the toolbar is hidden. - if (!isVisible()) { - updateTabCount(count); - return; - } - - // If toolbar is selected, this means the entry is expanded and the - // tabs button is translated offscreen. Don't trigger tabs counter - // updates until the tabs button is back on screen. - // See fromAwesomeBarSearch() - if (!mLayout.isSelected()) { - mTabsCounter.setCount(count); - - mTabs.setContentDescription((count > 1) ? - mActivity.getString(R.string.num_tabs, count) : - mActivity.getString(R.string.one_tab)); - } - } - public void updateTabCount(int count) { // If toolbar is selected, this means the entry is expanded and the // tabs button is translated offscreen. Don't trigger tabs counter @@ -777,7 +755,14 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, return; } - mTabsCounter.setCurrentText(String.valueOf(count)); + // Set TabCounter based on visibility + if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0) { + mTabsCounter.setCountWithAnimation(count); + } else { + mTabsCounter.setCount(count); + } + + // Update A11y information mTabs.setContentDescription((count > 1) ? mActivity.getString(R.string.num_tabs, count) : mActivity.getString(R.string.one_tab)); @@ -1056,7 +1041,7 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener, ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mForward.getLayoutParams(); - layoutParams.leftMargin = mDefaultForwardMargin + (mForward.isEnabled() ? mForward.getWidth() / 2 : 0); + layoutParams.leftMargin = mDefaultForwardMargin + (mForward.isEnabled() ? width : 0); ViewHelper.setTranslationX(mForward, 0); mUrlDisplayContainer.requestLayout(); diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index c12ae99d7d4c..a29fb1424fcb 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1277,7 +1277,14 @@ public class GeckoAppShell if (android.os.Build.VERSION.SDK_INT >= 11) { android.content.ClipboardManager cm = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(ClipData.newPlainText("Text", text)); + ClipData clip = ClipData.newPlainText("Text", text); + try { + cm.setPrimaryClip(clip); + } catch (NullPointerException e) { + // Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw + // a NullPointerException if Samsung's /data/clipboard directory is full. + // Fortunately, the text is still successfully copied to the clipboard. + } } else { android.text.ClipboardManager cm = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); diff --git a/mobile/android/base/GeckoPreferences.java b/mobile/android/base/GeckoPreferences.java index 0ed1eabfa0f6..08d5a5d415b4 100644 --- a/mobile/android/base/GeckoPreferences.java +++ b/mobile/android/base/GeckoPreferences.java @@ -6,6 +6,7 @@ package org.mozilla.gecko; import org.mozilla.gecko.background.announcements.AnnouncementsConstants; +import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.gecko.GeckoPreferenceFragment; import org.mozilla.gecko.util.ThreadUtils; @@ -289,6 +290,9 @@ public class GeckoPreferences * Broadcast an intent with pref, branch, and * enabled extras. This is intended to represent the * notification of a preference value to observers. + * + * The broadcast will be sent only to receivers registered with the + * (Fennec-specific) per-Android package permission. */ public static void broadcastPrefAction(final Context context, final String action, @@ -300,7 +304,7 @@ public class GeckoPreferences intent.putExtra("branch", GeckoApp.PREFS_NAME); intent.putExtra("enabled", value); Log.d(LOGTAG, "Broadcast: " + action + ", " + pref + ", " + GeckoApp.PREFS_NAME + ", " + value); - context.sendBroadcast(intent); + context.sendBroadcast(intent, GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION); } /** diff --git a/mobile/android/base/TabCounter.java b/mobile/android/base/TabCounter.java index 3dab6e5751c8..d6b984f8cbf1 100644 --- a/mobile/android/base/TabCounter.java +++ b/mobile/android/base/TabCounter.java @@ -50,7 +50,6 @@ public class TabCounter extends GeckoTextSwitcher removeAllViews(); setFactory(this); - setCount(0); if (Build.VERSION.SDK_INT >= 16) { // This adds the TextSwitcher to the a11y node tree, where we in turn @@ -66,21 +65,30 @@ public class TabCounter extends GeckoTextSwitcher } } - public void setCount(int count) { - if (mCount > count) { - setInAnimation(mFlipInBackward); - setOutAnimation(mFlipOutForward); - } else if (mCount < count) { - setInAnimation(mFlipInForward); - setOutAnimation(mFlipOutBackward); - } else { + public void setCountWithAnimation(int count) { + if (mCount == count) return; + + // Don't animate from initial state + if (mCount != 0) { + if (count < mCount) { + setInAnimation(mFlipInBackward); + setOutAnimation(mFlipOutForward); + } else if (count > mCount) { + setInAnimation(mFlipInForward); + setOutAnimation(mFlipOutBackward); + } } setText(String.valueOf(count)); mCount = count; } + public void setCount(int count) { + setCurrentText(String.valueOf(count)); + mCount = count; + } + private AnimationSet createAnimation(float startAngle, float endAngle, FadeMode fadeMode, float zEnd, boolean reverse) { diff --git a/mobile/android/base/background/common/GlobalConstants.java.in b/mobile/android/base/background/common/GlobalConstants.java.in index bc9bc8dd87c8..aa97cb900f7a 100644 --- a/mobile/android/base/background/common/GlobalConstants.java.in +++ b/mobile/android/base/background/common/GlobalConstants.java.in @@ -29,6 +29,13 @@ public class GlobalConstants { public static final String BROWSER_INTENT_PACKAGE = "@ANDROID_PACKAGE_NAME@"; public static final String BROWSER_INTENT_CLASS = BROWSER_INTENT_PACKAGE + ".App"; + /** + * Bug 800244: this signing-level permission protects broadcast intents that + * should be received only by the Firefox versions with the given Android + * package name. + */ + public static final String PER_ANDROID_PACKAGE_PERMISSION = "@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE"; + public static final int SHARED_PREFERENCES_MODE = 0; // These are used to ask Fennec (via reflection) to send // us a pref notification. This avoids us having to guess diff --git a/mobile/android/base/background/healthreport/HealthReportGenerator.java b/mobile/android/base/background/healthreport/HealthReportGenerator.java index 029fdbc61a47..0c05e1f8bace 100644 --- a/mobile/android/base/background/healthreport/HealthReportGenerator.java +++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java @@ -4,10 +4,8 @@ package org.mozilla.gecko.background.healthreport; -import java.util.HashMap; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; +import org.json.JSONException; +import org.json.JSONObject; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field; @@ -32,8 +30,9 @@ public class HealthReportGenerator { /** * @return null if no environment could be computed, or else the resulting document. + * @throws JSONException if there was an error adding environment data to the resulting document. */ - public JSONObject generateDocument(long since, long lastPingTime, String profilePath) { + public JSONObject generateDocument(long since, long lastPingTime, String profilePath) throws JSONException { Logger.info(LOG_TAG, "Generating FHR document from " + since + "; last ping " + lastPingTime + ", for profile " + profilePath); ProfileInformationCache cache = new ProfileInformationCache(profilePath); if (!cache.restoreUnlessInitialized()) { @@ -55,8 +54,9 @@ public class HealthReportGenerator { * * * days is a map from date strings to {hash: {measurement: {_v: version, fields...}}}. + * @throws JSONException if there was an error adding environment data to the resulting document. */ - public JSONObject generateDocument(long since, long lastPingTime, Environment currentEnvironment) { + public JSONObject generateDocument(long since, long lastPingTime, Environment currentEnvironment) throws JSONException { Logger.debug(LOG_TAG, "Current environment hash: " + currentEnvironment.getHash()); // We want to map field IDs to some strings as we go. @@ -64,26 +64,21 @@ public class HealthReportGenerator { JSONObject document = new JSONObject(); - // Defeat "unchecked" warnings with JDK7. See Bug 875088. - @SuppressWarnings("unchecked") - HashMap doc = ((HashMap) document); - if (lastPingTime >= HealthReportConstants.EARLIEST_LAST_PING) { - doc.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime)); + document.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime)); } - doc.put("thisPingDate", HealthReportUtils.getDateString(now())); - doc.put("version", PAYLOAD_VERSION); + document.put("thisPingDate", HealthReportUtils.getDateString(now())); + document.put("version", PAYLOAD_VERSION); - doc.put("environments", getEnvironmentsJSON(currentEnvironment, envs)); - doc.put("data", getDataJSON(currentEnvironment, envs, since)); + document.put("environments", getEnvironmentsJSON(currentEnvironment, envs)); + document.put("data", getDataJSON(currentEnvironment, envs, since)); return document; } - @SuppressWarnings("unchecked") protected JSONObject getDataJSON(Environment currentEnvironment, - SparseArray envs, long since) { + SparseArray envs, long since) throws JSONException { SparseArray fields = storage.getFieldsByID(); JSONObject days = getDaysJSON(currentEnvironment, envs, fields, since); @@ -96,8 +91,7 @@ public class HealthReportGenerator { return data; } - @SuppressWarnings("unchecked") - protected JSONObject getDaysJSON(Environment currentEnvironment, SparseArray envs, SparseArray fields, long since) { + protected JSONObject getDaysJSON(Environment currentEnvironment, SparseArray envs, SparseArray fields, long since) throws JSONException { JSONObject days = new JSONObject(); Cursor cursor = storage.getRawEventsSince(since); try { @@ -140,7 +134,7 @@ public class HealthReportGenerator { } final Field field = fields.get(cField); - JSONObject measurement = (JSONObject) envObject.get(field.measurementName); + JSONObject measurement = envObject.optJSONObject(field.measurementName); if (measurement == null) { // We will never have more than one measurement version within a // single environment -- to do so involves changing the build ID. And @@ -151,15 +145,10 @@ public class HealthReportGenerator { envObject.put(field.measurementName, measurement); } if (field.isDiscreteField()) { - JSONArray discrete = (JSONArray) measurement.get(field.fieldName); - if (discrete == null) { - discrete = new JSONArray(); - measurement.put(field.fieldName, discrete); - } if (field.isStringField()) { - discrete.add(cursor.getString(3)); + HealthReportUtils.append(measurement, field.fieldName, cursor.getString(3)); } else if (field.isIntegerField()) { - discrete.add(cursor.getLong(3)); + HealthReportUtils.append(measurement, field.fieldName, cursor.getLong(3)); } else { // Uh oh! throw new IllegalStateException("Unknown field type: " + field.flags); @@ -182,9 +171,8 @@ public class HealthReportGenerator { return days; } - @SuppressWarnings("unchecked") protected JSONObject getEnvironmentsJSON(Environment currentEnvironment, - SparseArray envs) { + SparseArray envs) throws JSONException { JSONObject environments = new JSONObject(); // Always do this, even if it hasn't recorded anything in the DB. @@ -201,8 +189,7 @@ public class HealthReportGenerator { return environments; } - @SuppressWarnings("unchecked") - private JSONObject jsonify(Environment e, Environment current) { + private JSONObject jsonify(Environment e, Environment current) throws JSONException { JSONObject age = getProfileAge(e, current); JSONObject sysinfo = getSysInfo(e, current); JSONObject gecko = getGeckoInfo(e, current); @@ -231,8 +218,7 @@ public class HealthReportGenerator { return out; } - @SuppressWarnings("unchecked") - private JSONObject getProfileAge(Environment e, Environment current) { + private JSONObject getProfileAge(Environment e, Environment current) throws JSONException { JSONObject age = new JSONObject(); int changes = 0; if (current == null || current.profileCreation != e.profileCreation) { @@ -246,8 +232,7 @@ public class HealthReportGenerator { return age; } - @SuppressWarnings("unchecked") - private JSONObject getSysInfo(Environment e, Environment current) { + private JSONObject getSysInfo(Environment e, Environment current) throws JSONException { JSONObject sysinfo = new JSONObject(); int changes = 0; if (current == null || current.cpuCount != e.cpuCount) { @@ -277,8 +262,7 @@ public class HealthReportGenerator { return sysinfo; } - @SuppressWarnings("unchecked") - private JSONObject getGeckoInfo(Environment e, Environment current) { + private JSONObject getGeckoInfo(Environment e, Environment current) throws JSONException { JSONObject gecko = new JSONObject(); int changes = 0; if (current == null || !current.vendor.equals(e.vendor)) { @@ -328,8 +312,7 @@ public class HealthReportGenerator { return gecko; } - @SuppressWarnings("unchecked") - private JSONObject getAppInfo(Environment e, Environment current) { + private JSONObject getAppInfo(Environment e, Environment current) throws JSONException { JSONObject appinfo = new JSONObject(); int changes = 0; if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) { @@ -347,8 +330,7 @@ public class HealthReportGenerator { return appinfo; } - @SuppressWarnings("unchecked") - private JSONObject getAddonCounts(Environment e, Environment current) { + private JSONObject getAddonCounts(Environment e, Environment current) throws JSONException { JSONObject counts = new JSONObject(); int changes = 0; if (current == null || current.extensionCount != e.extensionCount) { @@ -370,8 +352,7 @@ public class HealthReportGenerator { return counts; } - @SuppressWarnings("unchecked") - private JSONObject getActiveAddons(Environment e, Environment current) { + private JSONObject getActiveAddons(Environment e, Environment current) throws JSONException { JSONObject active = new JSONObject(); int changes = 0; if (current != null && changes == 0) { diff --git a/mobile/android/base/background/healthreport/HealthReportUtils.java b/mobile/android/base/background/healthreport/HealthReportUtils.java index d61761eee610..1510af77b5b0 100644 --- a/mobile/android/base/background/healthreport/HealthReportUtils.java +++ b/mobile/android/base/background/healthreport/HealthReportUtils.java @@ -5,9 +5,17 @@ package org.mozilla.gecko.background.healthreport; import java.text.SimpleDateFormat; +import java.util.HashSet; +import java.util.Iterator; import java.util.Locale; +import java.util.Set; +import java.util.SortedSet; import java.util.TimeZone; +import java.util.TreeSet; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; import org.mozilla.apache.commons.codec.digest.DigestUtils; import android.content.ContentUris; @@ -45,4 +53,88 @@ public class HealthReportUtils { public static Uri getEventURI(Uri environmentURI) { return environmentURI.buildUpon().path("/events/" + ContentUris.parseId(environmentURI) + "/").build(); } + + /** + * Copy the keys from the provided {@link JSONObject} into the provided {@link Set}. + */ + private static > T intoKeySet(T keys, JSONObject o) { + if (o == null || o == JSONObject.NULL) { + return keys; + } + + @SuppressWarnings("unchecked") + Iterator it = o.keys(); + while (it.hasNext()) { + keys.add(it.next()); + } + return keys; + } + + /** + * Produce a {@link SortedSet} containing the string keys of the provided + * object. + * + * @param o a {@link JSONObject} with string keys. + * @return a sorted set. + */ + public static SortedSet sortedKeySet(JSONObject o) { + return intoKeySet(new TreeSet(), o); + } + + /** + * Produce a {@link Set} containing the string keys of the provided object. + * @param o a {@link JSONObject} with string keys. + * @return an unsorted set. + */ + public static Set keySet(JSONObject o) { + return intoKeySet(new HashSet(), o); + } + + /** + * {@link JSONObject} doesn't provide a clone method, nor any + * useful constructors, so we do this the hard way. + * + * @return a new object containing the same keys and values as the old. + * @throws JSONException + * if JSONObject is even more stupid than expected and cannot store + * a value from the provided object in the new object. This should + * never happen. + */ + public static JSONObject shallowCopyObject(JSONObject o) throws JSONException { + if (o == null) { + return null; + } + + JSONObject out = new JSONObject(); + @SuppressWarnings("unchecked") + Iterator keys = out.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + out.put(key, o.get(key)); + } + return out; + } + + /** + * Just like {@link JSONObject#accumulate(String, Object)}, but doesn't do the wrong thing for single values. + * @throws JSONException + */ + public static void append(JSONObject o, String key, Object value) throws JSONException { + if (!o.has(key)) { + JSONArray arr = new JSONArray(); + arr.put(value); + o.put(key, arr); + return; + } + Object dest = o.get(key); + if (dest instanceof JSONArray) { + ((JSONArray) dest).put(value); + return; + } + JSONArray arr = new JSONArray(); + arr.put(dest); + arr.put(value); + o.put(key, arr); + return; + } } diff --git a/mobile/android/base/resources/values-xlarge-v11/dimens.xml b/mobile/android/base/resources/values-xlarge-v11/dimens.xml index b0275651c214..6d3aae0ab246 100644 --- a/mobile/android/base/resources/values-xlarge-v11/dimens.xml +++ b/mobile/android/base/resources/values-xlarge-v11/dimens.xml @@ -21,6 +21,5 @@ 26sp 60dp 8dip - 84dip diff --git a/mobile/android/chrome/content/aboutDownloads.js b/mobile/android/chrome/content/aboutDownloads.js index 829e7d26eef0..11b2282183f8 100644 --- a/mobile/android/chrome/content/aboutDownloads.js +++ b/mobile/android/chrome/content/aboutDownloads.js @@ -149,9 +149,9 @@ let Downloads = { this._stepAddEntries(privateEntries, this._privateList, privateEntries.length); // Add non-private downloads - let normalEntries = this.getDownloads({ isPrivate: false }); - this._stepAddEntries(normalEntries, this._normalList, 1); - ContextMenus.init(); + let normalEntries = this.getDownloads({ isPrivate: false }); + this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this)); + ContextMenus.init(); }, uninit: function dl_uninit() { @@ -358,6 +358,7 @@ let Downloads = { let updatedState = this._getState(aStmt.row.state); // Try to get the attribute values from the statement + return { guid: aStmt.row.guid, target: aStmt.row.name, @@ -377,9 +378,14 @@ let Downloads = { } }, - _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems) { - if (aEntries.length == 0) + _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) { + + if (aEntries.length == 0){ + if (aCallback) + aCallback(); + return; + } let attrs = aEntries.shift(); let item = this._createItem(downloadTemplate, attrs); @@ -388,12 +394,12 @@ let Downloads = { // Add another item to the list if we should; otherwise, let the UI update // and continue later if (aNumItems > 1) { - this._stepAddEntries(aEntries, aList, aNumItems - 1); + this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback); } else { // Use a shorter delay for earlier downloads to display them faster let delay = Math.min(aList.itemCount * 10, 300); setTimeout(function () { - this._stepAddEntries(aEntries, aList, 5); + this._stepAddEntries(aEntries, aList, 5, aCallback); }.bind(this), delay); } }, @@ -552,6 +558,31 @@ let Downloads = { this.logError("_updateDownloadRow() " + ex, aDownload); } }, + + /** + * In case a specific downloadId was passed while opening, scrolls the list to + * the given elemenet + */ + + _scrollToSelectedDownload : function dl_scrollToSelected() { + let spec = document.location.href; + let pos = spec.indexOf("?"); + let query = ""; + if (pos >= 0) + query = spec.substring(pos + 1); + + // Just assume the query is "id=" + let id = query.substring(3); + if (!id) { + return; + } + downloadElement = this._getElementForDownload(id); + if (!downloadElement) { + return; + } + + downloadElement.scrollIntoView(); + }, /** * Logs the error to the console. diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index b40f690fd9cd..9048ed83b5a9 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -57,6 +57,16 @@ XPCOMUtils.defineLazyGetter(this, "Sanitizer", function() { return Sanitizer; }); +XPCOMUtils.defineLazyGetter(this, "Prompt", function() { + let temp = {}; + Cu.import("resource://gre/modules/Prompt.jsm", temp); + return temp.Prompt; +}); + +XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", + "@mozilla.org/uuid-generator;1", + "nsIUUIDGenerator"); + // Lazily-loaded browser scripts: [ ["HelperApps", "chrome://browser/content/HelperApps.js"], @@ -238,6 +248,7 @@ var BrowserApp = { _tabs: [], _selectedTab: null, _prefObservers: [], + _promptHandlers: {}, get isTablet() { let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); @@ -287,6 +298,7 @@ var BrowserApp = { Services.obs.addObserver(this, "FormHistory:Init", false); Services.obs.addObserver(this, "gather-telemetry", false); Services.obs.addObserver(this, "keyword-search", false); + Services.obs.addObserver(this, "Prompt:Reply", false); Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); @@ -1412,6 +1424,18 @@ var BrowserApp = { browser.contentDocument.mozCancelFullScreen(); break; + case "Prompt:Reply": + { + let data = JSON.parse(aData); + let guid = data.guid; + let handler = this._promptHandlers[guid]; + if (!handler) + break; + this._promptHandlers[guid]; + handler(data); + } + break; + case "Viewport:Change": if (this.isBrowserContentDocumentDisplayed()) this.selectedTab.setViewport(JSON.parse(aData)); @@ -1476,9 +1500,12 @@ var BrowserApp = { // selecting selIndex(if fromIndex<=selIndex<=toIndex) showHistory: function(fromIndex, toIndex, selIndex) { let browser = this.selectedBrowser; + let guid = uuidgen.generateUUID().toString(); let result = { type: "Prompt:Show", multiple: false, + async: true, + guid: guid, selected: [], listitems: [] }; @@ -1495,11 +1522,13 @@ var BrowserApp = { result.listitems.push(item); result.selected.push(i == selIndex); } - let data = JSON.parse(sendMessageToJava(result)); - let selected = data.button; - if (selected == -1) - return; - browser.gotoIndex(toIndex-selected); + this._promptHandlers[guid] = function (data) { + let selected = data.button; + if (selected == -1) + return; + browser.gotoIndex(toIndex-selected); + }; + sendMessageToJava(result); }, }; diff --git a/mobile/android/chrome/content/downloads.js b/mobile/android/chrome/content/downloads.js index c55541a1cc45..ac980b5b8f16 100644 --- a/mobile/android/chrome/content/downloads.js +++ b/mobile/android/chrome/content/downloads.js @@ -37,11 +37,17 @@ var Downloads = { Services.obs.addObserver(this, "last-pb-context-exited", true); }, - openDownload: function dl_openDownload(aFileURI) { - let f = this._getLocalFile(aFileURI); + openDownload: function dl_openDownload(aDownload) { + let fileUri = aDownload.target.spec; + let guid = aDownload.guid; + let f = this._getLocalFile(fileUri); try { f.launch(); - } catch (ex) { } + } catch (ex) { + // in case we are not able to open the file (i.e. there is no app able to handle it) + // we just open the browser tab showing it + BrowserApp.addTab("about:downloads?id=" + guid); + } }, cancelDownload: function dl_cancelDownload(aDownload) { @@ -65,7 +71,7 @@ var Downloads = { if (aTopic == "alertclickcallback") { if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { // Only open the downloaded file if the download is complete - self.openDownload(aDownload.target.spec); + self.openDownload(aDownload); } else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING && !cancelPrompt) { cancelPrompt = true; diff --git a/mobile/android/modules/Makefile.in b/mobile/android/modules/Makefile.in index 143e188453f7..14156b254420 100644 --- a/mobile/android/modules/Makefile.in +++ b/mobile/android/modules/Makefile.in @@ -15,6 +15,7 @@ EXTRA_JS_MODULES = \ OrderedBroadcast.jsm \ Sanitizer.jsm \ SharedPreferences.jsm \ + Prompt.jsm \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/mobile/android/modules/Prompt.jsm b/mobile/android/modules/Prompt.jsm new file mode 100644 index 000000000000..1e9acb4b858b --- /dev/null +++ b/mobile/android/modules/Prompt.jsm @@ -0,0 +1,169 @@ +/* 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" + +let Cc = Components.classes; +let Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +this.EXPORTED_SYMBOLS = ["Prompt"]; + +function log(msg) { + //Services.console.logStringMessage(msg); +} + +function Prompt(aOptions) { + this.window = "window" in aOptions ? aOptions.window : null; + this.msg = { type: "Prompt:Show", async: true }; + + if ("title" in aOptions) + this.msg.title = aOptions.title; + + if ("message" in aOptions) + this.msg.text = aOptions.message; + + if ("buttons" in aOptions) + this.msg.buttons = aOptions.buttons; + + let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); + this.guid = idService.generateUUID().toString(); + this.msg.guid = this.guid; +} + +Prompt.prototype = { + addButton: function(aOptions) { + if (!this.msg.buttons) + this.msg.buttons = []; + this.msg.buttons.push(aOptions.label); + return this; + }, + + _addInput: function(aOptions) { + let obj = aOptions; + if (this[aOptions.type + "_count"] === undefined) + this[aOptions.type + "_count"] = 0; + + obj.id = aOptions.id || (aOptions.type + this[aOptions.type + "_count"]); + this[aOptions.type + "_count"]++; + + this.msg.inputs.push(obj); + return this; + }, + + addCheckbox: function(aOptions) { + return this._addInput({ + type: "checkbox", + label: aOptions.label, + checked: aOptions.checked, + id: aOptions.id + }); + }, + + addTextbox: function(aOptions) { + return this._addInput({ + type: "textbox", + value: aOptions.value, + hint: aOptions.hint, + autofocus: aOptions.autofocus, + id: aOptions.id + }); + }, + + addPassword: function(aOptions) { + return this._addInput({ + type: "password", + value: aOptions.value, + hint: aOptions.hint, + autofocus: aOptions.autofocus, + id : aOptions.id + }); + }, + + addMenulist: function(aOptions) { + return this._addInput({ + type: "menulist", + values: aOptions.values, + id: aOptions.id + }); + }, + + show: function(callback) { + this.callback = callback; + log("Sending message"); + Services.obs.addObserver(this, "Prompt:Reply", false); + this._innerShow(); + }, + + _innerShow: function() { + this.bridge.handleGeckoMessage(JSON.stringify(this.msg)); + }, + + observe: function(aSubject, aTopic, aData) { + log("observe " + aData); + let data = JSON.parse(aData); + if (data.guid != this.guid) + return; + + Services.obs.removeObserver(this, "Prompt:Reply", false); + + if (this.callback) + this.callback(data); + }, + + _setListItems: function(aItems, aInGroup) { + let hasSelected = false; + if (!aInGroup) + this.msg.listitems = []; + + aItems.forEach(function(item) { + let obj = { id: item.id }; + + if (aInGroup !== undefined) + obj.inGroup = aInGroup; + + obj.label = item.label; + + if (item.disabled) + obj.disabled = true; + + if (item.selected || hasSelected || this.msg.multiple) { + if (!this.msg.selected) { + this.msg.selected = new Array(this.msg.listitems.length); + hasSelected = true; + } + this.msg.selected[this.msg.listitems.length] = item.selected; + } + + if (item.children) { + obj.isGroup = true; + } else if (item.submenu) { + obj.isParent = true; + } + + // Order matters in the java message, so make sure we add the obj + // to the list before we add its children + this.msg.listitems.push(obj); + + if (item.children) + this._setListItems(item.children, true); + + }, this); + return this; + }, + + setSingleChoiceItems: function(aItems) { + return this._setListItems(aItems); + }, + + setMultiChoiceItems: function(aItems) { + this.msg.multiple = true; + return this._setListItems(aItems); + }, + + get bridge() { + return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge); + }, + +} diff --git a/mobile/android/services/manifests/AnnouncementsAndroidManifest_services.xml.in b/mobile/android/services/manifests/AnnouncementsAndroidManifest_services.xml.in index 904ac605b794..9b2526fde7cd 100644 --- a/mobile/android/services/manifests/AnnouncementsAndroidManifest_services.xml.in +++ b/mobile/android/services/manifests/AnnouncementsAndroidManifest_services.xml.in @@ -6,3 +6,5 @@ android:exported="false" android:name="org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService" > + + diff --git a/mobile/android/services/manifests/SyncAndroidManifest_permissions.xml.in b/mobile/android/services/manifests/SyncAndroidManifest_permissions.xml.in index 4f2f0b912083..d54e87c529cf 100644 --- a/mobile/android/services/manifests/SyncAndroidManifest_permissions.xml.in +++ b/mobile/android/services/manifests/SyncAndroidManifest_permissions.xml.in @@ -17,3 +17,16 @@ + + + diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index a34898b0a814..114acdf4c489 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -152,6 +152,9 @@ pref("media.cache_size", 512000); // Master HTML5 media volume scale. pref("media.volume_scale", "1.0"); +// Timeout for wakelock release +pref("media.wakelock_timeout", 2000); + #ifdef MOZ_WMF pref("media.windows-media-foundation.enabled", true); pref("media.windows-media-foundation.use-dxva", true); @@ -1017,6 +1020,8 @@ pref("network.http.spdy.persistent-settings", false); pref("network.http.spdy.ping-threshold", 58); pref("network.http.spdy.ping-timeout", 8); pref("network.http.spdy.send-buffer-size", 131072); +pref("network.http.spdy.allow-push", true); +pref("network.http.spdy.push-allowance", 65536); pref("network.http.diagnostics", false); diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp index 98032170b271..81d27b915e8d 100644 --- a/mozglue/android/APKOpen.cpp +++ b/mozglue/android/APKOpen.cpp @@ -27,6 +27,7 @@ #include "APKOpen.h" #include #include +#include #include "Zip.h" #include "sqlite3.h" #include "SQLiteBridge.h" @@ -39,6 +40,20 @@ #define RUSAGE_THREAD 1 #endif +#ifndef RELEASE_BUILD +/* Official builds have the debuggable flag set to false, which disables + * the backtrace dumper from bionic. However, as it is useful for native + * crashes happening before the crash reporter is registered, re-enable + * it on non release builds (i.e. nightly and aurora). + * Using a constructor so that it is re-enabled as soon as libmozglue.so + * is loaded. + */ +__attribute__((constructor)) +void make_dumpable() { + prctl(PR_SET_DUMPABLE, 1); +} +#endif + extern "C" { /* * To work around http://code.google.com/p/android/issues/detail?id=23203 @@ -154,8 +169,9 @@ loadGeckoLibs(const char *apkName) chdir(getenv("GRE_HOME")); uint64_t t0 = TimeStamp_Now(); - struct rusage usage1; - getrusage(RUSAGE_THREAD, &usage1); + struct rusage usage1_thread, usage1; + getrusage(RUSAGE_THREAD, &usage1_thread); + getrusage(RUSAGE_SELF, &usage1); RefPtr zip = ZipCollection::GetZip(apkName); @@ -177,14 +193,22 @@ loadGeckoLibs(const char *apkName) xul_dlsym("XRE_StartupTimelineRecord", &XRE_StartupTimelineRecord); uint64_t t1 = TimeStamp_Now(); - struct rusage usage2; - getrusage(RUSAGE_THREAD, &usage2); + struct rusage usage2_thread, usage2; + getrusage(RUSAGE_THREAD, &usage2_thread); + getrusage(RUSAGE_SELF, &usage2); - __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %lldms total, %ldms user, %ldms system, %ld faults", - (t1 - t0) / 1000, - (usage2.ru_utime.tv_sec - usage1.ru_utime.tv_sec)*1000 + (usage2.ru_utime.tv_usec - usage1.ru_utime.tv_usec)/1000, - (usage2.ru_stime.tv_sec - usage1.ru_stime.tv_sec)*1000 + (usage2.ru_stime.tv_usec - usage1.ru_stime.tv_usec)/1000, - usage2.ru_majflt-usage1.ru_majflt); +#define RUSAGE_TIMEDIFF(u1, u2, field) \ + ((u2.ru_ ## field.tv_sec - u1.ru_ ## field.tv_sec) * 1000 + \ + (u2.ru_ ## field.tv_usec - u1.ru_ ## field.tv_usec) / 1000) + + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %lldms total, %ldms(%ldms) user, %ldms(%ldms) system, %ld(%ld) faults", + (t1 - t0) / 1000000, + RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, utime), + RUSAGE_TIMEDIFF(usage1, usage2, utime), + RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, stime), + RUSAGE_TIMEDIFF(usage1, usage2, stime), + usage2_thread.ru_majflt - usage1_thread.ru_majflt, + usage2.ru_majflt - usage1.ru_majflt); XRE_StartupTimelineRecord(LINKER_INITIALIZED, t0); XRE_StartupTimelineRecord(LIBRARIES_LOADED, t1); diff --git a/netwerk/base/public/nsILoadGroup.idl b/netwerk/base/public/nsILoadGroup.idl index a01e4981c726..0579d601a315 100644 --- a/netwerk/base/public/nsILoadGroup.idl +++ b/netwerk/base/public/nsILoadGroup.idl @@ -80,11 +80,18 @@ interface nsILoadGroup : nsIRequest readonly attribute nsILoadGroupConnectionInfo connectionInfo; }; +%{C++ +#include "mozilla/net/PSpdyPush3.h" +%} + +[ptr] native SpdyPushCache3Ptr(mozilla::net::SpdyPushCache3); + /** * Used to maintain state about the connections of a load group and * how they interact with blocking items like HEAD css/js loads. */ -[uuid(d1f9f18e-3d85-473a-ad58-a2367d7cdb2a)] + +[uuid(5361f30e-f968-437c-8f41-69d2756a6022)] interface nsILoadGroupConnectionInfo : nsISupports { /** @@ -104,4 +111,11 @@ interface nsILoadGroupConnectionInfo : nsISupports * blockers. */ unsigned long removeBlockingTransaction(); + + /* reading this attribute gives out weak pointers to the push + * cache. The nsILoadGroupConnectionInfo implemenation owns the cache + * and will destroy it when overwritten or when the load group + * ends. + */ + [noscript] attribute SpdyPushCache3Ptr spdyPushCache3; }; diff --git a/netwerk/base/src/nsLoadGroup.cpp b/netwerk/base/src/nsLoadGroup.cpp index f7dc4503385e..3096f4b5f53b 100644 --- a/netwerk/base/src/nsLoadGroup.cpp +++ b/netwerk/base/src/nsLoadGroup.cpp @@ -1054,6 +1054,7 @@ public: nsLoadGroupConnectionInfo(); private: int32_t mBlockingTransactionCount; // signed for PR_ATOMIC_* + nsAutoPtr mSpdyCache3; }; NS_IMPL_THREADSAFE_ISUPPORTS1(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo) @@ -1087,6 +1088,20 @@ nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval) return NS_OK; } +/* [noscript] attribute SpdyPushCache3Ptr spdyPushCache3; */ +NS_IMETHODIMP +nsLoadGroupConnectionInfo::GetSpdyPushCache3(mozilla::net::SpdyPushCache3 **aSpdyPushCache3) +{ + *aSpdyPushCache3 = mSpdyCache3.get(); + return NS_OK; +} +NS_IMETHODIMP +nsLoadGroupConnectionInfo::SetSpdyPushCache3(mozilla::net::SpdyPushCache3 *aSpdyPushCache3) +{ + mSpdyCache3 = aSpdyPushCache3; + return NS_OK; +} + nsresult nsLoadGroup::Init() { static PLDHashTableOps hash_table_ops = diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 7715d849361b..17a3f6094cc5 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -17,6 +17,7 @@ #include "nsTransportUtils.h" #include "nsProxyInfo.h" #include "nsNetCID.h" +#include "nsNetUtil.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "netCore.h" @@ -1858,10 +1859,15 @@ nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks) NS_IMETHODIMP nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks) { + nsCOMPtr threadsafeCallbacks; + NS_NewNotificationCallbacksAggregation(callbacks, nullptr, + NS_GetCurrentThread(), + getter_AddRefs(threadsafeCallbacks)); + nsCOMPtr secinfo; { MutexAutoLock lock(mLock); - mCallbacks = callbacks; + mCallbacks = threadsafeCallbacks; SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", mSecInfo.get(), mCallbacks.get())); @@ -1871,7 +1877,7 @@ nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks) // don't call into PSM while holding mLock!! nsCOMPtr secCtrl(do_QueryInterface(secinfo)); if (secCtrl) - secCtrl->SetNotificationCallbacks(callbacks); + secCtrl->SetNotificationCallbacks(threadsafeCallbacks); return NS_OK; } diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h index 8753a70118bd..6fc630065eb6 100644 --- a/netwerk/protocol/http/ASpdySession.h +++ b/netwerk/protocol/http/ASpdySession.h @@ -42,6 +42,12 @@ public: const static uint32_t kSendingChunkSize = 4096; const static uint32_t kTCPSendBufferSize = 131072; + + // until we have an API that can push back on receiving data (right now + // WriteSegments is obligated to accept data and buffer) there is no + // reason to throttle with the rwin other than in server push + // scenarios. + const static uint32_t kInitialRwin = 256 * 1024 * 1024; }; // this is essentially a single instantiation as a member of nsHttpHandler. diff --git a/netwerk/protocol/http/PSpdyPush3.h b/netwerk/protocol/http/PSpdyPush3.h new file mode 100644 index 000000000000..1a914131ad60 --- /dev/null +++ b/netwerk/protocol/http/PSpdyPush3.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// SPDY Server Push as defined by +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +/* + A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer) + and spooled there until a GET is made that can be matched up with it. At + that time we have two spdy streams - the GET (aka the sink) and the PUSH + (aka the source). Data is copied between those two streams for the lifetime + of the transaction. This is true even if the transaction buffer is empty, + partly complete, or totally loaded at the time the GET correspondence is made. + + correspondence is done through a hash table of the full url, the spdy session, + and the load group. The load group is implicit because that's where the + hash is stored, the other items comprise the hash key. + + Pushed streams are subject to aggressive flow control before they are matched + with a GET at which point flow control is effectively disabled to match the + client pull behavior. +*/ + +#ifndef mozilla_net_SpdyPush3_Public_h +#define mozilla_net_SpdyPush3_Public_h + +#include "nsAutoPtr.h" +#include "nsDataHashtable.h" +#include "nsISupports.h" + +class nsCString; + +namespace mozilla { +namespace net { + +class SpdyPushedStream3; + +// One Cache per load group +class SpdyPushCache3 +{ +public: + SpdyPushCache3(); + virtual ~SpdyPushCache3(); + + // The cache holds only weak pointers - no references + bool RegisterPushedStream(nsCString key, + SpdyPushedStream3 *stream); + SpdyPushedStream3 *RemovePushedStream(nsCString key); + SpdyPushedStream3 *GetPushedStream(nsCString key); + +private: + nsDataHashtable mHash; +}; + +} // namespace mozilla::net +} // namespace mozilla + +#endif // mozilla_net_SpdyPush3_Public_h diff --git a/netwerk/protocol/http/SpdyPush3.cpp b/netwerk/protocol/http/SpdyPush3.cpp new file mode 100644 index 000000000000..7f92239076b0 --- /dev/null +++ b/netwerk/protocol/http/SpdyPush3.cpp @@ -0,0 +1,395 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* 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/. */ + +#include + +#include "nsDependentString.h" +#include "SpdyPush3.h" + +namespace mozilla { +namespace net { + +////////////////////////////////////////// +// SpdyPushedStream3 +////////////////////////////////////////// + +SpdyPushedStream3::SpdyPushedStream3(SpdyPush3TransactionBuffer *aTransaction, + SpdySession3 *aSession, + SpdyStream3 *aAssociatedStream, + uint32_t aID) + :SpdyStream3(aTransaction, aSession, + 0 /* priority is only for sending, so ignore it on push */) + , mConsumerStream(nullptr) + , mBufferedPush(aTransaction) + , mStatus(NS_OK) + , mPushCompleted(false) + , mDeferCleanupOnSuccess(true) +{ + LOG3(("SpdyPushedStream3 ctor this=%p 0x%X\n", this, aID)); + mStreamID = aID; + mBufferedPush->SetPushStream(this); + mLoadGroupCI = aAssociatedStream->LoadGroupConnectionInfo(); + mLastRead = TimeStamp::Now(); +} + +bool +SpdyPushedStream3::GetPushComplete() +{ + return mPushCompleted; +} + +nsresult +SpdyPushedStream3::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, + uint32_t *countWritten) +{ + nsresult rv = SpdyStream3::WriteSegments(writer, count, countWritten); + if (NS_SUCCEEDED(rv) && *countWritten) { + mLastRead = TimeStamp::Now(); + } + + if (rv == NS_BASE_STREAM_CLOSED) { + mPushCompleted = true; + rv = NS_OK; // this is what a normal HTTP transaction would do + } + if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) + mStatus = rv; + return rv; +} + +nsresult +SpdyPushedStream3::ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *count) +{ + // The SYN_STREAM for this has been processed, so we need to verify + // that :host, :scheme, and :path MUST be present + nsDependentCSubstring host, scheme, path; + nsresult rv; + + rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":host"), host); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X " + "push without required :host\n", mSession, mStreamID)); + return rv; + } + + rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":scheme"), scheme); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X " + "push without required :scheme\n", mSession, mStreamID)); + return rv; + } + + rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":path"), path); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X " + "push without required :host\n", mSession, mStreamID)); + return rv; + } + + CreatePushHashKey(nsCString(scheme), nsCString(host), + mSession->Serial(), path, + mOrigin, mHashKey); + + LOG3(("SpdyPushStream3 0x%X hash key %s\n", mStreamID, mHashKey.get())); + + // the write side of a pushed transaction just involves manipulating a little state + SpdyStream3::mSentFinOnData = 1; + SpdyStream3::mSynFrameComplete = 1; + SpdyStream3::ChangeState(UPSTREAM_COMPLETE); + *count = 0; + return NS_OK; +} + +bool +SpdyPushedStream3::GetHashKey(nsCString &key) +{ + if (mHashKey.IsEmpty()) + return false; + + key = mHashKey; + return true; +} + +void +SpdyPushedStream3::ConnectPushedStream(SpdyStream3 *stream) +{ + mSession->ConnectPushedStream(stream); +} + +bool +SpdyPushedStream3::IsOrphaned(TimeStamp now) +{ + MOZ_ASSERT(!now.IsNull()); + + // if spdy is not transmitting, and is also not connected to a consumer + // stream, and its been like that for too long then it is oprhaned + + if (mConsumerStream) + return false; + + bool rv = ((now - mLastRead).ToSeconds() > 30.0); + if (rv) { + LOG3(("SpdyPushCache3::IsOrphaned 0x%X IsOrphaned %3.2f\n", + mStreamID, (now - mLastRead).ToSeconds())); + } + return rv; +} + +nsresult +SpdyPushedStream3::GetBufferedData(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + if (!*countWritten) + rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK; + + return rv; +} + +////////////////////////////////////////// +// SpdyPushCache3 +////////////////////////////////////////// + +SpdyPushCache3::SpdyPushCache3() +{ + mHash.Init(); +} + +SpdyPushCache3::~SpdyPushCache3() +{ + mHash.Clear(); +} + +SpdyPushedStream3 * +SpdyPushCache3::GetPushedStream(nsCString key) +{ + return mHash.Get(key); +} + +bool +SpdyPushCache3::RegisterPushedStream(nsCString key, + SpdyPushedStream3 *stream) +{ + LOG3(("SpdyPushCache3::RegisterPushedStream %s 0x%X\n", + key.get(), stream->StreamID())); + if(mHash.Get(key)) + return false; + mHash.Put(key, stream); + return true; +} + +SpdyPushedStream3 * +SpdyPushCache3::RemovePushedStream(nsCString key) +{ + SpdyPushedStream3 *rv = mHash.Get(key); + LOG3(("SpdyPushCache3::RemovePushedStream %s 0x%X\n", + key.get(), rv ? rv->StreamID() : 0)); + if (rv) + mHash.Remove(key); + return rv; +} + +////////////////////////////////////////// +// SpdyPush3TransactionBuffer +// This is the nsAHttpTransction owned by the stream when the pushed +// stream has not yet been matched with a pull request +////////////////////////////////////////// + +NS_IMPL_THREADSAFE_ISUPPORTS0(SpdyPush3TransactionBuffer) + +SpdyPush3TransactionBuffer::SpdyPush3TransactionBuffer() + : mStatus(NS_OK) + , mRequestHead(nullptr) + , mPushStream(nullptr) + , mIsDone(false) + , mBufferedHTTP1Size(kDefaultBufferSize) + , mBufferedHTTP1Used(0) + , mBufferedHTTP1Consumed(0) +{ + mBufferedHTTP1 = new char[mBufferedHTTP1Size]; +} + +SpdyPush3TransactionBuffer::~SpdyPush3TransactionBuffer() +{ + delete mRequestHead; +} + +void +SpdyPush3TransactionBuffer::SetConnection(nsAHttpConnection *conn) +{ +} + +nsAHttpConnection * +SpdyPush3TransactionBuffer::Connection() +{ + return nullptr; +} + +void +SpdyPush3TransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB) +{ + *outCB = nullptr; +} + +void +SpdyPush3TransactionBuffer::OnTransportStatus(nsITransport* transport, + nsresult status, uint64_t progress) +{ +} + +bool +SpdyPush3TransactionBuffer::IsDone() +{ + return mIsDone; +} + +nsresult +SpdyPush3TransactionBuffer::Status() +{ + return mStatus; +} + +uint32_t +SpdyPush3TransactionBuffer::Caps() +{ + return 0; +} + +uint64_t +SpdyPush3TransactionBuffer::Available() +{ + return mBufferedHTTP1Used - mBufferedHTTP1Consumed; +} + +nsresult +SpdyPush3TransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t count, uint32_t *countRead) +{ + *countRead = 0; + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +SpdyPush3TransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, uint32_t *countWritten) +{ + if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) { + SpdySession3::EnsureBuffer(mBufferedHTTP1, + mBufferedHTTP1Size + kDefaultBufferSize, + mBufferedHTTP1Used, + mBufferedHTTP1Size); + } + + count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used); + nsresult rv = writer->OnWriteSegment(mBufferedHTTP1 + mBufferedHTTP1Used, + count, countWritten); + if (NS_SUCCEEDED(rv)) { + mBufferedHTTP1Used += *countWritten; + } + else if (rv == NS_BASE_STREAM_CLOSED) { + mIsDone = true; + } + + if (Available()) { + SpdyStream3 *consumer = mPushStream->GetConsumerStream(); + + if (consumer) { + LOG3(("SpdyPush3TransactionBuffer::WriteSegments notifying connection " + "consumer data available 0x%X [%u]\n", + mPushStream->StreamID(), Available())); + mPushStream->ConnectPushedStream(consumer); + } + } + + return rv; +} + +uint32_t +SpdyPush3TransactionBuffer::Http1xTransactionCount() +{ + return 0; +} + +nsHttpRequestHead * +SpdyPush3TransactionBuffer::RequestHead() +{ + if (!mRequestHead) + mRequestHead = new nsHttpRequestHead(); + return mRequestHead; +} + +nsresult +SpdyPush3TransactionBuffer::TakeSubTransactions( + nsTArray > &outTransactions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +SpdyPush3TransactionBuffer::SetProxyConnectFailed() +{ +} + +void +SpdyPush3TransactionBuffer::Close(nsresult reason) +{ + mStatus = reason; + mIsDone = true; +} + +nsresult +SpdyPush3TransactionBuffer::AddTransaction(nsAHttpTransaction *trans) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t +SpdyPush3TransactionBuffer::PipelineDepth() +{ + return 0; +} + +nsresult +SpdyPush3TransactionBuffer::SetPipelinePosition(int32_t position) +{ + return NS_OK; +} + +int32_t +SpdyPush3TransactionBuffer::PipelinePosition() +{ + return 1; +} + +nsresult +SpdyPush3TransactionBuffer::GetBufferedData(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + *countWritten = std::min(count, static_cast(Available())); + if (*countWritten) { + memcpy(buf, mBufferedHTTP1 + mBufferedHTTP1Consumed, *countWritten); + mBufferedHTTP1Consumed += *countWritten; + } + + // If all the data has been consumed then reset the buffer + if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) { + mBufferedHTTP1Consumed = 0; + mBufferedHTTP1Used = 0; + } + + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla diff --git a/netwerk/protocol/http/SpdyPush3.h b/netwerk/protocol/http/SpdyPush3.h new file mode 100644 index 000000000000..b3fa0dbd2b8d --- /dev/null +++ b/netwerk/protocol/http/SpdyPush3.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// SPDY Server Push as defined by +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +#ifndef mozilla_net_SpdyPush3_Internal_h +#define mozilla_net_SpdyPush3_Internal_h + +#include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" +#include "nsHttpRequestHead.h" +#include "nsILoadGroup.h" +#include "nsString.h" +#include "PSpdyPush3.h" +#include "SpdySession3.h" +#include "SpdyStream3.h" + +namespace mozilla { +namespace net { + +class SpdyPush3TransactionBuffer; + +class SpdyPushedStream3 MOZ_FINAL : public SpdyStream3 +{ +public: + SpdyPushedStream3(SpdyPush3TransactionBuffer *aTransaction, + SpdySession3 *aSession, + SpdyStream3 *aAssociatedStream, + uint32_t aID); + virtual ~SpdyPushedStream3() {} + + bool GetPushComplete(); + SpdyStream3 *GetConsumerStream() { return mConsumerStream; }; + void SetConsumerStream(SpdyStream3 *aStream) { mConsumerStream = aStream; } + bool GetHashKey(nsCString &key); + + // override of SpdyStream3 + nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); + nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); + + nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI; }; + void ConnectPushedStream(SpdyStream3 *consumer); + + bool DeferCleanupOnSuccess() { return mDeferCleanupOnSuccess; } + void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; } + + bool IsOrphaned(TimeStamp now); + + nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten); + + // overload of SpdyStream3 + virtual bool HasSink() { return !!mConsumerStream; } + +private: + + SpdyStream3 *mConsumerStream; // paired request stream that consumes from + // real spdy one.. null until a match is made. + + nsCOMPtr mLoadGroupCI; + + SpdyPush3TransactionBuffer *mBufferedPush; + mozilla::TimeStamp mLastRead; + + nsCString mHashKey; + nsresult mStatus; + bool mPushCompleted; // server push FIN received + bool mDeferCleanupOnSuccess; +}; + +class SpdyPush3TransactionBuffer MOZ_FINAL : public nsAHttpTransaction +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + + SpdyPush3TransactionBuffer(); + virtual ~SpdyPush3TransactionBuffer(); + + nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten); + void SetPushStream(SpdyPushedStream3 *stream) { mPushStream = stream; } + +private: + const static uint32_t kDefaultBufferSize = 4096; + + nsresult mStatus; + nsHttpRequestHead *mRequestHead; + SpdyPushedStream3 *mPushStream; + bool mIsDone; + + nsAutoArrayPtr mBufferedHTTP1; + uint32_t mBufferedHTTP1Size; + uint32_t mBufferedHTTP1Used; + uint32_t mBufferedHTTP1Consumed; +}; + +} // namespace mozilla::net +} // namespace mozilla + +#endif // mozilla_net_SpdyPush3_Internal_h diff --git a/netwerk/protocol/http/SpdySession3.cpp b/netwerk/protocol/http/SpdySession3.cpp index f1a7a54dedf6..40c0bc32fd03 100644 --- a/netwerk/protocol/http/SpdySession3.cpp +++ b/netwerk/protocol/http/SpdySession3.cpp @@ -4,15 +4,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "nsHttp.h" -#include "SpdySession3.h" -#include "SpdyStream3.h" -#include "nsHttpConnection.h" -#include "nsHttpHandler.h" -#include "prnetdb.h" #include "mozilla/Telemetry.h" #include "mozilla/Preferences.h" +#include "nsHttp.h" +#include "nsHttpHandler.h" +#include "nsHttpConnection.h" +#include "nsILoadGroup.h" #include "prprf.h" +#include "prnetdb.h" +#include "SpdyPush3.h" +#include "SpdySession3.h" +#include "SpdyStream3.h" + #include #ifdef DEBUG @@ -38,7 +41,6 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, : mSocketTransport(aSocketTransport), mSegmentReader(nullptr), mSegmentWriter(nullptr), - mSendingChunkSize(ASpdySession::kSendingChunkSize), mNextStreamID(1), mConcurrentHighWater(0), mDownstreamState(BUFFERING_FRAME_HEADER), @@ -65,8 +67,11 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); - LOG3(("SpdySession3::SpdySession3 %p transaction 1 = %p", - this, aHttpTransaction)); + static uint64_t sSerial; + mSerial = ++sSerial; + + LOG3(("SpdySession3::SpdySession3 %p transaction 1 = %p serial=0x%X\n", + this, aHttpTransaction, mSerial)); mStreamIDHash.Init(); mStreamTransactionHash.Init(); @@ -75,6 +80,7 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, mOutputQueueBuffer = new char[mOutputQueueSize]; zlibInit(); + mPushAllowance = gHttpHandler->SpdyPushAllowance(); mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); GenerateSettings(); @@ -116,9 +122,12 @@ SpdySession3::GoAwayEnumerator(nsAHttpTransaction *key, // these streams were not processed by the server and can be restarted. // Do that after the enumerator completes to avoid the risk of - // a restart event re-entrantly modifying this hash. - if (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID()) + // a restart event re-entrantly modifying this hash. Be sure not to restart + // a pushed (even numbered) stream + if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) || + !stream->HasRegisteredID()) { self->mGoAwayStreamsToRestart.Push(stream); + } return PL_DHASH_NEXT; } @@ -187,7 +196,8 @@ static Control_FX sControlFunctions[] = SpdySession3::HandlePing, SpdySession3::HandleGoAway, SpdySession3::HandleHeaders, - SpdySession3::HandleWindowUpdate + SpdySession3::HandleWindowUpdate, + SpdySession3::HandleCredential }; bool @@ -219,12 +229,12 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now) MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); MOZ_ASSERT(mNextPingID & 1, "Ping Counter Not Odd"); - if (!mPingThreshold) - return; - LOG(("SpdySession3::ReadTimeoutTick %p delta since last read %ds\n", this, PR_IntervalToSeconds(now - mLastReadEpoch))); + if (!mPingThreshold) + return; + if ((now - mLastReadEpoch) < mPingThreshold) { // recent activity means ping is not an issue if (mPingSentEpoch) @@ -259,6 +269,35 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now) mNextPingID += 2; ResumeRecv(); // read the ping reply + // Check for orphaned push streams. This looks expensive, but generally the + // list is empty. + SpdyPushedStream3 *deleteMe; + TimeStamp timestampNow; + do { + deleteMe = nullptr; + + for (uint32_t index = mPushedStreams.Length(); + index > 0 ; --index) { + SpdyPushedStream3 *pushedStream = mPushedStreams[index - 1]; + + if (timestampNow.IsNull()) + timestampNow = TimeStamp::Now(); // lazy initializer + + // if spdy finished, but not connected, and its been like that for too long.. + // cleanup the stream.. + if (pushedStream->IsOrphaned(timestampNow)) + { + LOG3(("SpdySession3 Timeout Pushed Stream %p 0x%X\n", + this, pushedStream->StreamID())); + deleteMe = pushedStream; + break; // don't CleanupStream() while iterating this vector + } + } + if (deleteMe) + CleanupStream(deleteMe, NS_ERROR_ABORT, RST_CANCEL); + + } while (deleteMe); + if (mNextPingID == 0xffffffff) { LOG(("SpdySession3::ReadTimeoutTick %p " "ping ids exhausted marking goaway\n", this)); @@ -267,35 +306,41 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now) } uint32_t -SpdySession3::RegisterStreamID(SpdyStream3 *stream) +SpdySession3::RegisterStreamID(SpdyStream3 *stream, uint32_t aNewID) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); - LOG3(("SpdySession3::RegisterStreamID session=%p stream=%p id=0x%X " - "concurrent=%d",this, stream, mNextStreamID, mConcurrent)); - MOZ_ASSERT(mNextStreamID < 0xfffffff0, "should have stopped admitting streams"); - uint32_t result = mNextStreamID; - mNextStreamID += 2; + MOZ_ASSERT(!(aNewID & 1), + "0 for autoassign pull, otherwise explicit even push assignment"); + if (!aNewID) { + // auto generate a new pull stream ID + aNewID = mNextStreamID; + MOZ_ASSERT(aNewID & 1, "pull ID must be odd."); + mNextStreamID += 2; + } + + LOG3(("SpdySession3::RegisterStreamID session=%p stream=%p id=0x%X " + "concurrent=%d",this, stream, aNewID, mConcurrent)); // We've used up plenty of ID's on this session. Start // moving to a new one before there is a crunch involving // server push streams or concurrent non-registered submits - if (mNextStreamID >= kMaxStreamID) + if (aNewID >= kMaxStreamID) mShouldGoAway = true; // integrity check - if (mStreamIDHash.Get(result)) { + if (mStreamIDHash.Get(aNewID)) { LOG3((" New ID already present\n")); MOZ_ASSERT(false, "New ID already present in mStreamIDHash"); mShouldGoAway = true; return kDeadStreamID; } - mStreamIDHash.Put(result, stream); - return result; + mStreamIDHash.Put(aNewID, stream); + return aNewID; } bool @@ -312,12 +357,7 @@ SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction, } aHttpTransaction->SetConnection(this); - SpdyStream3 *stream = new SpdyStream3(aHttpTransaction, - this, - mSocketTransport, - mSendingChunkSize, - &mUpstreamZlib, - aPriority); + SpdyStream3 *stream = new SpdyStream3(aHttpTransaction, this, aPriority); LOG3(("SpdySession3::AddStream session=%p stream=%p NextID=0x%X (tentative)", this, stream, mNextStreamID)); @@ -330,8 +370,7 @@ SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction, ActivateStream(stream); } else { - LOG3(("SpdySession3::AddStream %p stream %p queued.", - this, stream)); + LOG3(("SpdySession3::AddStream %p stream %p queued.", this, stream)); mQueuedStreams.Push(stream); } @@ -342,8 +381,10 @@ void SpdySession3::ActivateStream(SpdyStream3 *stream) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), + "Do not activate pushed streams"); - mConcurrent++; + ++mConcurrent; if (mConcurrent > mConcurrentHighWater) mConcurrentHighWater = mConcurrent; LOG3(("SpdySession3::AddStream %p activating stream %p Currently %d " @@ -485,9 +526,9 @@ SpdySession3::ResetDownstreamState() if (mInputFrameDataLast && mInputFrameDataStream) { mInputFrameDataLast = false; if (!mInputFrameDataStream->RecvdFin()) { + LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID())); mInputFrameDataStream->SetRecvdFin(true); - --mConcurrent; - ProcessPending(); + DecrementConcurrent(mInputFrameDataStream); } } mInputFrameBufferUsed = 0; @@ -528,6 +569,21 @@ SpdySession3::EnsureBuffer(nsAutoArrayPtr &buf, uint32_t preserve, uint32_t &objSize); +void +SpdySession3::DecrementConcurrent(SpdyStream3 *aStream) +{ + uint32_t id = aStream->StreamID(); + + if (id && !(id & 0x1)) + return; // pushed streams aren't counted in concurrent limit + + MOZ_ASSERT(mConcurrent); + --mConcurrent; + LOG3(("DecrementConcurrent %p id=0x%X concurrent=%d\n", + this, id, mConcurrent)); + ProcessPending(); +} + void SpdySession3::zlibInit() { @@ -691,11 +747,13 @@ SpdySession3::GenerateSettings() // 2nd entry is bytes 20 to 27 // 3rd entry is bytes 28 to 35 + if (!gHttpHandler->AllowSpdyPush()) { // announcing that we accept 0 incoming streams is done to - // disable server push until that is implemented. - packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT; - // The value portion of the setting pair is already initialized to 0 - numberOfEntries++; + // disable server push + packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT; + // The value portion of the setting pair is already initialized to 0 + numberOfEntries++; + } nsRefPtr ci; uint32_t cwnd = 0; @@ -711,8 +769,11 @@ SpdySession3::GenerateSettings() numberOfEntries++; } + // Advertise the Push RWIN and on each client SYN_STREAM pipeline + // a window update with it in order to use larger initial windows with pulled + // streams. packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW; - uint32_t rwin = PR_htonl(kInitialRwin); + uint32_t rwin = PR_htonl(mPushAllowance); memcpy(packet + 16 + 8 * numberOfEntries, &rwin, 4); numberOfEntries++; @@ -779,6 +840,7 @@ SpdySession3::VerifyStream(SpdyStream3 *aStream, uint32_t aOptionalID = 0) "optionalID=0x%X trans=%p test=%d\n", this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(), test)); + MOZ_ASSERT(false, "VerifyStream"); return false; } @@ -791,24 +853,39 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult, LOG3(("SpdySession3::CleanupStream %p %p 0x%X %X\n", this, aStream, aStream->StreamID(), aResult)); + SpdyPushedStream3 *pushSource = nullptr; + + if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) { + LOG(("SpdySession3::CleanupStream 0x%X deferred\n", aStream->StreamID())); + return; + } + if (!VerifyStream(aStream)) { LOG(("SpdySession3::CleanupStream failed to verify stream\n")); return; } + pushSource = aStream->PushSource(); + if (!aStream->RecvdFin() && aStream->StreamID()) { LOG3(("Stream had not processed recv FIN, sending RST code %X\n", aResetCode)); GenerateRstStream(aResetCode, aStream->StreamID()); - --mConcurrent; - ProcessPending(); + DecrementConcurrent(aStream); } CloseStream(aStream, aResult); - // Remove the stream from the ID hash table. (this one isn't short, which is - // why it is hashed.) - mStreamIDHash.Remove(aStream->StreamID()); + // Remove the stream from the ID hash table and, if an even id, the pushed + // table too. + uint32_t id = aStream->StreamID(); + if (id > 0) { + mStreamIDHash.Remove(id); + if (!(id & 1)) + mPushedStreams.RemoveElement(aStream); + } + + RemoveStreamFromQueues(aStream); // removing from the stream transaction hash will // delete the SpdyStream3 and drop the reference to @@ -817,6 +894,29 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult, if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK); + + if (pushSource) { + pushSource->SetDeferCleanupOnSuccess(false); + CleanupStream(pushSource, aResult, aResetCode); + } +} + +static void RemoveStreamFromQueue(SpdyStream3 *aStream, nsDeque &queue) +{ + uint32_t size = queue.GetSize(); + for (uint32_t count = 0; count < size; ++count) { + SpdyStream3 *stream = static_cast(queue.PopFront()); + if (stream != aStream) + queue.Push(stream); + } +} + +void +SpdySession3::RemoveStreamFromQueues(SpdyStream3 *aStream) +{ + RemoveStreamFromQueue(aStream, mReadyForWrite); + RemoveStreamFromQueue(aStream, mQueuedStreams); + RemoveStreamFromQueue(aStream, mReadyForRead); } void @@ -833,23 +933,7 @@ SpdySession3::CloseStream(SpdyStream3 *aStream, nsresult aResult) mInputFrameDataStream = nullptr; } - // check the streams blocked on write, this is linear but the list - // should be pretty short. - uint32_t size = mReadyForWrite.GetSize(); - for (uint32_t count = 0; count < size; ++count) { - SpdyStream3 *stream = static_cast(mReadyForWrite.PopFront()); - if (stream != aStream) - mReadyForWrite.Push(stream); - } - - // Check the streams queued for activation. Because we normally accept a high - // level of parallelization this should also be short. - size = mQueuedStreams.GetSize(); - for (uint32_t count = 0; count < size; ++count) { - SpdyStream3 *stream = static_cast(mQueuedStreams.PopFront()); - if (stream != aStream) - mQueuedStreams.Push(stream); - } + RemoveStreamFromQueues(aStream); // Send the stream the close() indication aStream->Close(aResult); @@ -870,9 +954,10 @@ SpdySession3::HandleSynStream(SpdySession3 *self) PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); uint32_t associatedID = PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); + uint8_t flags = reinterpret_cast(self->mInputFrameBuffer.get())[4]; LOG3(("SpdySession3::HandleSynStream %p recv SYN_STREAM (push) " - "for ID 0x%X associated with 0x%X.", + "for ID 0x%X associated with 0x%X.\n", self, streamID, associatedID)); if (streamID & 0x01) { // test for odd stream ID @@ -881,6 +966,12 @@ SpdySession3::HandleSynStream(SpdySession3 *self) return NS_ERROR_ILLEGAL_VALUE; } + // confirm associated-to + nsresult rv = self->SetInputFrameDataStream(associatedID); + if (NS_FAILED(rv)) + return rv; + SpdyStream3 *associatedStream = self->mInputFrameDataStream; + ++(self->mServerPushedResources); // Anytime we start using the high bit of stream ID (either client or server) @@ -888,17 +979,127 @@ SpdySession3::HandleSynStream(SpdySession3 *self) if (streamID >= kMaxStreamID) self->mShouldGoAway = true; - // Need to decompress the headers even though we aren't using them yet in - // order to keep the compression context consistent for other syn_reply frames - nsresult rv = - self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10); + bool resetStream = true; + SpdyPushCache3 *cache = nullptr; + + if (!(flags & kFlag_Data_UNI)) { + // pushed streams require UNIDIRECTIONAL flag + LOG3(("SpdySession3::HandleSynStream %p ID %0x%X associated ID 0x%X failed.\n", + self, streamID, associatedID)); + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + + } else if (!associatedID) { + // associated stream 0 will never find a match, but the spec requires a + // PROTOCOL_ERROR in this specific case + LOG3(("SpdySession3::HandleSynStream %p associated ID of 0 failed.\n", self)); + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + + } else if (!gHttpHandler->AllowSpdyPush()) { + // MAX_CONCURRENT_STREAMS of 0 in settings should have disabled push, + // but some servers are buggy about that.. or the config could have + // been updated after the settings frame was sent. In both cases just + // reject the pushed stream as refused + LOG3(("SpdySession3::HandleSynStream Push Recevied when Disabled\n")); + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + + } else if (!associatedStream) { + LOG3(("SpdySession3::HandleSynStream %p lookup associated ID failed.\n", self)); + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + } else { + nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo(); + if (loadGroupCI) { + loadGroupCI->GetSpdyPushCache3(&cache); + if (!cache) { + cache = new SpdyPushCache3(); + if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache3(cache))) { + delete cache; + cache = nullptr; + } + } + } + if (!cache) { + // this is unexpected, but we can handle it just be refusing the push + LOG3(("SpdySession3::HandleSynStream Push Recevied without loadgroup cache\n")); + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + } + else { + resetStream = false; + } + } + + if (resetStream) { + // Need to decompress the headers even though we aren't using them yet in + // order to keep the compression context consistent for other syn_reply frames + rv = self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10); + if (NS_FAILED(rv)) { + LOG(("SpdySession3::HandleSynStream uncompress failed\n")); + return rv; + } + self->ResetDownstreamState(); + return NS_OK; + } + + // Create the buffering transaction and push stream + nsRefPtr transactionBuffer = + new SpdyPush3TransactionBuffer(); + transactionBuffer->SetConnection(self); + SpdyPushedStream3 *pushedStream = + new SpdyPushedStream3(transactionBuffer, self, + associatedStream, streamID); + + // ownership of the pushed stream is by the transaction hash, just as it + // is for a client initiated stream. Errors that aren't fatal to the + // whole session must call cleanupStream() after this point in order + // to remove the stream from that hash. + self->mStreamTransactionHash.Put(transactionBuffer, pushedStream); + self->mPushedStreams.AppendElement(pushedStream); + + // The pushed stream is unidirectional so it is fully open immediately + pushedStream->SetFullyOpen(); + + // Uncompress the response headers into a stream specific buffer, leaving them + // in spdy format for the time being. + rv = pushedStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 18, + self->mInputFrameDataSize - 10); if (NS_FAILED(rv)) { LOG(("SpdySession3::HandleSynStream uncompress failed\n")); return rv; } - // todo populate cache. For now, just reject server push p3 - self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + if (self->RegisterStreamID(pushedStream, streamID) == kDeadStreamID) { + LOG(("SpdySession3::HandleSynStream registerstreamid failed\n")); + return NS_ERROR_FAILURE; + } + + // Fake the request side of the pushed HTTP transaction. Sets up hash + // key and origin + uint32_t notUsed; + pushedStream->ReadSegments(nullptr, 1, ¬Used); + + nsAutoCString key; + if (!pushedStream->GetHashKey(key)) { + LOG(("SpdySession3::HandleSynStream one of :host :scheme :path missing from push\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!associatedStream->Origin().Equals(pushedStream->Origin())) { + LOG(("SpdySession3::HandleSynStream pushed stream mismatched origin\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!cache->RegisterPushedStream(key, pushedStream)) { + LOG(("SpdySession3::HandleSynStream registerPushedStream Failed\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + self->ResetDownstreamState(); return NS_OK; } @@ -1266,8 +1467,8 @@ SpdySession3::HandleGoAway(SpdySession3 *self) self->mCleanShutdown = true; // Find streams greater than the last-good ID and mark them for deletion - // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. They can - // be restarted. + // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The + // underlying transaction can be restarted. self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self); // Process the streams marked for deletion and restart. @@ -1298,7 +1499,6 @@ SpdySession3::HandleGoAway(SpdySession3 *self) PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]), self->mStreamTransactionHash.Count())); - self->ResumeRecv(); self->ResetDownstreamState(); return NS_OK; } @@ -1330,7 +1530,7 @@ SpdySession3::HandleHeaders(SpdySession3 *self) if (NS_FAILED(self->UncompressAndDiscard(12, self->mInputFrameDataSize - 4))) { - LOG(("SpdySession3::HandleSynReply uncompress failed\n")); + LOG(("SpdySession3::HandleHeaders uncompress failed\n")); // this is fatal to the session return NS_ERROR_FAILURE; } @@ -1429,7 +1629,19 @@ SpdySession3::HandleWindowUpdate(SpdySession3 *self) } self->ResetDownstreamState(); - self->ResumeRecv(); + return NS_OK; +} + +nsresult +SpdySession3::HandleCredential(SpdySession3 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_CREDENTIAL); + + // These aren't used yet. Just ignore the frame. + + LOG3(("SpdySession3::HandleCredential %p NOP.", self)); + + self->ResetDownstreamState(); return NS_OK; } @@ -1454,8 +1666,9 @@ SpdySession3::OnTransportStatus(nsITransport* aTransport, case NS_NET_STATUS_CONNECTED_TO: { SpdyStream3 *target = mStreamIDHash.Get(1); - if (target) - target->Transaction()->OnTransportStatus(aTransport, aStatus, aProgress); + nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr; + if (transaction) + transaction->OnTransportStatus(aTransport, aStatus, aProgress); break; } @@ -1596,7 +1809,7 @@ SpdySession3::ReadSegments(nsAHttpSegmentReader *reader, // we call writer->OnWriteSegment via NetworkRead() to get a spdy header.. // and decide if it is data or control.. if it is control, just deal with it. // if it is data, identify the spdy stream -// call stream->WriteSegemnts which can call this::OnWriteSegment to get the +// call stream->WriteSegments which can call this::OnWriteSegment to get the // data. It always gets full frames if they are part of the stream nsresult @@ -1614,6 +1827,42 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, SetWriteCallbacks(); + // If there are http transactions attached to a push stream with filled buffers + // trigger that data pump here. This only reads from buffers (not the network) + // so mDownstreamState doesn't matter. + SpdyStream3 *pushConnectedStream = + static_cast(mReadyForRead.PopFront()); + if (pushConnectedStream) { + LOG3(("SpdySession3::WriteSegments %p processing pushed stream 0x%X\n", + this, pushConnectedStream->StreamID())); + mSegmentWriter = writer; + rv = pushConnectedStream->WriteSegments(this, count, countWritten); + mSegmentWriter = nullptr; + + // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK + // so we need this check to determine the truth. + if (NS_SUCCEEDED(rv) && !*countWritten && + pushConnectedStream->PushSource() && + pushConnectedStream->PushSource()->GetPushComplete()) { + rv = NS_BASE_STREAM_CLOSED; + } + + if (rv == NS_BASE_STREAM_CLOSED) { + CleanupStream(pushConnectedStream, NS_OK, RST_CANCEL); + rv = NS_OK; + } + + // if we return OK to nsHttpConnection it will use mSocketInCondition + // to determine whether to schedule more reads, incorrectly + // assuming that nsHttpConnection::OnSocketWrite() was called. + if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) { + rv = NS_BASE_STREAM_WOULD_BLOCK; + ResumeRecv(); + } + + return rv; + } + // We buffer all control frames and act on them in this layer. // We buffer the first 8 bytes of data frames (the header) but // the actual data is passed through unprocessed. @@ -1634,7 +1883,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, this, rv)); // maybe just blocked reading from network if (rv == NS_BASE_STREAM_WOULD_BLOCK) - ResumeRecv(); + rv = NS_OK; return rv; } @@ -1809,6 +2058,13 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, mNeedsCleanup = nullptr; } + if (NS_FAILED(rv)) { + LOG3(("SpdySession3 %p data frame read failure %x\n", this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + } + return rv; } @@ -1828,7 +2084,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, LOG3(("SpdySession3 %p discard frame read failure %x\n", this, rv)); // maybe just blocked reading from network if (rv == NS_BASE_STREAM_WOULD_BLOCK) - ResumeRecv(); + rv = NS_OK; return rv; } @@ -1841,9 +2097,9 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, return rv; } + MOZ_ASSERT(mDownstreamState == BUFFERING_CONTROL_FRAME); if (mDownstreamState != BUFFERING_CONTROL_FRAME) { // this cannot happen - MOZ_ASSERT(false, "Not in Bufering Control Frame State"); return NS_ERROR_UNEXPECTED; } @@ -1858,7 +2114,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, this, rv)); // maybe just blocked reading from network if (rv == NS_BASE_STREAM_WOULD_BLOCK) - ResumeRecv(); + rv = NS_OK; return rv; } @@ -1899,27 +2155,34 @@ SpdySession3::UpdateLocalRwin(SpdyStream3 *stream, if (!stream || stream->RecvdFin()) return; - LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X %d\n", - this, stream->StreamID(), bytes)); stream->DecrementLocalWindow(bytes); // Don't necessarily ack every data packet. Only do it // after a significant amount of data. uint64_t unacked = stream->LocalUnAcked(); + int64_t localWindow = stream->LocalWindow(); - if (unacked < kMinimumToAck) { - // Sanity check to make sure this won't let the window drop below 1MB - PR_STATIC_ASSERT(kMinimumToAck < kInitialRwin); - PR_STATIC_ASSERT((kInitialRwin - kMinimumToAck) > 1024 * 1024); + LOG3(("SpdySession3::UpdateLocalRwin this=%p id=0x%X newbytes=%u " + "unacked=%llu localWindow=%lld\n", + this, stream->StreamID(), bytes, unacked, localWindow)); + if (!unacked) + return; + + if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) + return; + + if (!stream->HasSink()) { + LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X Pushed Stream Has No Sink\n", + this, stream->StreamID())); return; } // Generate window updates directly out of spdysession instead of the stream - // in order to avoid queue delays in getting the ACK out. - uint32_t toack = unacked & 0x7fffffff; + // in order to avoid queue delays in getting the 'ACK' out. + uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU; - LOG3(("SpdySession3::UpdateLocalRwin Ack %p 0x%X %d\n", + LOG3(("SpdySession3::UpdateLocalRwin Ack this=%p id=0x%X acksize=%d\n", this, stream->StreamID(), toack)); stream->IncrementLocalWindow(toack); @@ -2186,6 +2449,13 @@ SpdySession3::SetNeedsCleanup() ResetDownstreamState(); } +void +SpdySession3::ConnectPushedStream(SpdyStream3 *stream) +{ + mReadyForRead.Push(stream); + ForceRecv(); +} + //----------------------------------------------------------------------------- // Modified methods of nsAHttpConnection //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/SpdySession3.h b/netwerk/protocol/http/SpdySession3.h index 6446e6f10871..7e215c0ddf07 100644 --- a/netwerk/protocol/http/SpdySession3.h +++ b/netwerk/protocol/http/SpdySession3.h @@ -10,14 +10,13 @@ // http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 #include "ASpdySession.h" +#include "mozilla/Attributes.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" #include "nsDeque.h" #include "nsHashKeys.h" #include "zlib.h" -#include "mozilla/Attributes.h" -class nsHttpConnection; class nsISocketTransport; namespace mozilla { namespace net { @@ -49,7 +48,8 @@ public: // Idle time represents time since "goodput".. e.g. a data or header frame PRIntervalTime IdleTime(); - uint32_t RegisterStreamID(SpdyStream3 *); + // Registering with a newID of 0 means pick the next available odd ID + uint32_t RegisterStreamID(SpdyStream3 *, uint32_t aNewID = 0); const static uint8_t kVersion = 3; @@ -134,11 +134,9 @@ public: // 31 bit stream ID. const static uint32_t kDeadStreamID = 0xffffdead; - // until we have an API that can push back on receiving data (right now - // WriteSegments is obligated to accept data and buffer) there is no - // reason to throttle with the rwin other than in server push - // scenarios. - const static uint32_t kInitialRwin = 256 * 1024 * 1024; + // below the emergency threshold of local window we ack every received + // byte. Above that we coalesce bytes into the MinimumToAck size. + const static int32_t kEmergencyWindowThreshold = 1024 * 1024; const static uint32_t kMinimumToAck = 64 * 1024; // The default peer rwin is 64KB unless updated by a settings frame @@ -153,6 +151,7 @@ public: static nsresult HandleGoAway(SpdySession3 *); static nsresult HandleHeaders(SpdySession3 *); static nsresult HandleWindowUpdate(SpdySession3 *); + static nsresult HandleCredential(SpdySession3 *); template static void EnsureBuffer(nsAutoArrayPtr &, @@ -173,8 +172,18 @@ public: uint32_t GetServerInitialWindow() { return mServerInitialWindow; } + void ConnectPushedStream(SpdyStream3 *stream); + + uint64_t Serial() { return mSerial; } + void PrintDiagnostics (nsCString &log); + // Streams need access to these + uint32_t SendingChunkSize() { return mSendingChunkSize; } + uint32_t PushAllowance() { return mPushAllowance; } + z_stream *UpstreamZlib() { return &mUpstreamZlib; } + nsISocketTransport *SocketTransport() { return mSocketTransport; } + private: enum stateType { @@ -191,6 +200,7 @@ private: void ChangeDownstreamState(enum stateType); void ResetDownstreamState(); nsresult UncompressAndDiscard(uint32_t, uint32_t); + void DecrementConcurrent(SpdyStream3 *); void zlibInit(); void GeneratePing(uint32_t); void GenerateRstStream(uint32_t, uint32_t); @@ -198,6 +208,7 @@ private: void CleanupStream(SpdyStream3 *, nsresult, rstReason); void CloseStream(SpdyStream3 *, nsresult); void GenerateSettings(); + void RemoveStreamFromQueues(SpdyStream3 *); void SetWriteCallbacks(); void FlushOutputQueue(); @@ -228,7 +239,7 @@ private: nsAutoPtr &, void *); - // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken + // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken // from the first transaction on this session. That object contains the // pointer to the real network-level nsHttpConnection object. nsRefPtr mConnection; @@ -245,20 +256,25 @@ private: uint32_t mSendingChunkSize; /* the transmission chunk size */ uint32_t mNextStreamID; /* 24 bits */ uint32_t mConcurrentHighWater; /* max parallelism on session */ + uint32_t mPushAllowance; /* rwin for unmatched pushes */ stateType mDownstreamState; /* in frame, between frames, etc.. */ - // Maintain 4 indexes - one by stream ID, one by transaction ptr, - // one list of streams ready to write, one list of streams that are queued - // due to max parallelism settings. The objects - // are not ref counted - they get destroyed + // Maintain 2 indexes - one by stream ID, one by transaction pointer. + // There are also several lists of streams: ready to write, queued due to + // max parallelism, streams that need to force a read for push, and the full + // set of pushed streams. + // The objects are not ref counted - they get destroyed // by the nsClassHashtable implementation when they are removed from // the transaction hash. nsDataHashtable mStreamIDHash; nsClassHashtable, SpdyStream3> mStreamTransactionHash; + nsDeque mReadyForWrite; nsDeque mQueuedStreams; + nsDeque mReadyForRead; + nsTArray mPushedStreams; // Compression contexts for header transport using deflate. // SPDY compresses only HTTP headers and does not reset zlib in between @@ -360,6 +376,11 @@ private: // used as a temporary buffer while enumerating the stream hash during GoAway nsDeque mGoAwayStreamsToRestart; + + // Each session gets a unique serial number because the push cache is correlated + // by the load group and the serial number can be used as part of the cache key + // to make sure streams aren't shared across sessions. + uint64_t mSerial; }; }} // namespace mozilla::net diff --git a/netwerk/protocol/http/SpdyStream3.cpp b/netwerk/protocol/http/SpdyStream3.cpp index 7b98c2e8c8a3..c55413d0b1f2 100644 --- a/netwerk/protocol/http/SpdyStream3.cpp +++ b/netwerk/protocol/http/SpdyStream3.cpp @@ -4,16 +4,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "nsHttp.h" -#include "SpdySession3.h" -#include "SpdyStream3.h" -#include "nsAlgorithm.h" -#include "prnetdb.h" -#include "nsHttpRequestHead.h" #include "mozilla/Telemetry.h" +#include "nsAlgorithm.h" +#include "nsHttp.h" +#include "nsHttpHandler.h" +#include "nsHttpRequestHead.h" #include "nsISocketTransport.h" #include "nsISupportsPriority.h" -#include "nsHttpHandler.h" +#include "prnetdb.h" +#include "SpdyPush3.h" +#include "SpdySession3.h" +#include "SpdyStream3.h" + #include #ifdef DEBUG @@ -26,21 +28,18 @@ namespace net { SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction, SpdySession3 *spdySession, - nsISocketTransport *socketTransport, - uint32_t chunkSize, - z_stream *compressionContext, int32_t priority) - : mUpstreamState(GENERATING_SYN_STREAM), - mTransaction(httpTransaction), + : mStreamID(0), mSession(spdySession), - mSocketTransport(socketTransport), + mUpstreamState(GENERATING_SYN_STREAM), + mSynFrameComplete(0), + mSentFinOnData(0), + mTransaction(httpTransaction), + mSocketTransport(spdySession->SocketTransport()), mSegmentReader(nullptr), mSegmentWriter(nullptr), - mStreamID(0), - mChunkSize(chunkSize), - mSynFrameComplete(0), + mChunkSize(spdySession->SendingChunkSize()), mRequestBlockedOnRead(0), - mSentFinOnData(0), mRecvdFin(0), mFullyOpen(0), mSentWaitingFor(0), @@ -49,23 +48,25 @@ SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction, mTxInlineFrameSize(SpdySession3::kDefaultBufferSize), mTxInlineFrameUsed(0), mTxStreamFrameSize(0), - mZlib(compressionContext), + mZlib(spdySession->UpstreamZlib()), mDecompressBufferSize(SpdySession3::kDefaultBufferSize), mDecompressBufferUsed(0), mDecompressedBytes(0), mRequestBodyLenRemaining(0), mPriority(priority), - mLocalWindow(SpdySession3::kInitialRwin), mLocalUnacked(0), mBlockedOnRwin(false), mTotalSent(0), - mTotalRead(0) + mTotalRead(0), + mPushSource(nullptr) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); LOG3(("SpdyStream3::SpdyStream3 %p", this)); mRemoteWindow = spdySession->GetServerInitialWindow(); + mLocalWindow = spdySession->PushAllowance(); + mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; mDecompressBuffer = new char[mDecompressBufferSize]; } @@ -186,15 +187,16 @@ SpdyStream3::WriteSegments(nsAHttpSegmentWriter *writer, uint32_t count, uint32_t *countWritten) { - LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x", - this, count, mUpstreamState)); - MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); + LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x", + this, count, mUpstreamState)); + mSegmentWriter = writer; - nsresult rv = mTransaction->WriteSegments(writer, count, countWritten); + nsresult rv = mTransaction->WriteSegments(this, count, countWritten); mSegmentWriter = nullptr; + return rv; } @@ -210,6 +212,26 @@ SpdyStream3::hdrHashEnumerate(const nsACString &key, return PL_DHASH_NEXT; } +void +SpdyStream3::CreatePushHashKey(const nsCString &scheme, + const nsCString &hostHeader, + uint64_t serial, + const nsCSubstring &pathInfo, + nsCString &outOrigin, + nsCString &outKey) +{ + outOrigin = scheme; + outOrigin.Append(NS_LITERAL_CSTRING("://")); + outOrigin.Append(hostHeader); + + outKey = outOrigin; + outKey.Append(NS_LITERAL_CSTRING("/[spdy3.")); + outKey.AppendInt(serial); + outKey.Append(NS_LITERAL_CSTRING("]")); + outKey.Append(pathInfo); +} + + nsresult SpdyStream3::ParseHttpRequestHeaders(const char *buf, uint32_t avail, @@ -247,6 +269,44 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, *countUsed = avail - (oldLen - endHeader) + 4; mSynFrameComplete = 1; + nsCString hostHeader; + nsCString hashkey; + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + + CreatePushHashKey(NS_LITERAL_CSTRING("https"), + hostHeader, mSession->Serial(), + mTransaction->RequestHead()->RequestURI(), + mOrigin, hashkey); + + // check the push cache for GET + if (mTransaction->RequestHead()->Method() == nsHttp::Get) { + // from :scheme, :host, :path + nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); + SpdyPushCache3 *cache = nullptr; + if (loadGroupCI) + loadGroupCI->GetSpdyPushCache3(&cache); + + SpdyPushedStream3 *pushedStream = nullptr; + // we remove the pushedstream from the push cache so that + // it will not be used for another GET. This does not destroy the + // stream itself - that is done when the transactionhash is done with it. + if (cache) + pushedStream = cache->RemovePushedStream(hashkey); + + if (pushedStream) { + LOG3(("Pushed Stream Match located id=0x%X key=%s\n", + pushedStream->StreamID(), hashkey.get())); + pushedStream->SetConsumerStream(this); + mPushSource = pushedStream; + mSentFinOnData = 1; + + // There is probably pushed data buffered so trigger a read manually + // as we can't rely on future network events to do it + mSession->ConnectPushedStream(this); + return NS_OK; + } + } + // It is now OK to assign a streamID that we are assured will // be monotonically increasing amongst syn-streams on this // session @@ -309,11 +369,6 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // The client cert "slot". Right now we don't send client certs mTxInlineFrame[17] = 0; - const char *methodHeader = mTransaction->RequestHead()->Method().get(); - - nsCString hostHeader; - mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); - nsCString versionHeader; if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) versionHeader = NS_LITERAL_CSTRING("HTTP/1.1"); @@ -388,6 +443,8 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // contain auth. The http transaction already logs the sanitized request // headers at this same level so it is not necessary to do so here. + const char *methodHeader = mTransaction->RequestHead()->Method().get(); + // The header block length uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */ CompressToFrame(count); @@ -452,6 +509,75 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, return NS_OK; } +void +SpdyStream3::AdjustInitialWindow() +{ + MOZ_ASSERT(mSession->PushAllowance() <= ASpdySession::kInitialRwin); + + // The session initial_window is sized for serverpushed streams. When we + // generate a client pulled stream we want to adjust the initial window + // to a huge value in a pipeline with that SYN_STREAM. + + // >0 even numbered IDs are pushed streams. + // odd numbered IDs are pulled streams. + // 0 is the sink for a pushed stream. + SpdyStream3 *stream = this; + if (!mStreamID) { + MOZ_ASSERT(mPushSource); + if (!mPushSource) + return; + stream = mPushSource; + MOZ_ASSERT(stream->mStreamID); + MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream + + // If the pushed stream has sent a FIN, there is no reason to update + // the window + if (stream->RecvdFin()) + return; + } + + // For server pushes we also want to include in the ack any data that has been + // buffered but unacknowledged. + + // mLocalUnacked is technically 64 bits, but because it can never grow larger than + // our window size (which is closer to 29bits max) we know it fits comfortably in 32. + // However we don't really enforce that, and track it as a 64 so that broken senders + // can still interoperate. That means we have to be careful with this calculation. + uint64_t toack64 = (ASpdySession::kInitialRwin - mSession->PushAllowance()) + + stream->mLocalUnacked; + stream->mLocalUnacked = 0; + if (toack64 > 0x7fffffff) { + stream->mLocalUnacked = toack64 - 0x7fffffff; + toack64 = 0x7fffffff; + } + uint32_t toack = static_cast(toack64); + if (!toack) + return; + toack = PR_htonl(toack); + + SpdySession3::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameUsed + 16, + mTxInlineFrameUsed, + mTxInlineFrameSize); + + unsigned char *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; + mTxInlineFrameUsed += 16; + + memset(packet, 0, 8); + packet[0] = SpdySession3::kFlag_Control; + packet[1] = SpdySession3::kVersion; + packet[3] = SpdySession3::CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = 8; // 8 data bytes after 8 byte header + + uint32_t id = PR_htonl(stream->mStreamID); + memcpy(packet + 8, &id, 4); + memcpy(packet + 12, &toack, 4); + + stream->mLocalWindow += PR_ntohl(toack); + LOG3(("AdjustInitialwindow %p 0x%X %u\n", + this, stream->mStreamID, PR_ntohl(toack))); +} + void SpdyStream3::UpdateTransportReadEvents(uint32_t count) { @@ -510,6 +636,16 @@ SpdyStream3::TransmitFrame(const char *buf, // flush internal buffers that were previously blocked on writing. You can // of course feed new data to it as well. + LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d", + this, mTxInlineFrameUsed, mTxStreamFrameSize)); + if (countUsed) + *countUsed = 0; + + if (!mTxInlineFrameUsed) { + MOZ_ASSERT(!buf); + return NS_OK; + } + MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), @@ -518,11 +654,6 @@ SpdyStream3::TransmitFrame(const char *buf, uint32_t transmittedCount; nsresult rv; - LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d", - this, mTxInlineFrameUsed, mTxStreamFrameSize)); - if (countUsed) - *countUsed = 0; - // In the (relatively common) event that we have a small amount of data // split between the inlineframe and the streamframe, then move the stream // data into the inlineframe via copy in order to coalesce into one write. @@ -862,6 +993,7 @@ SpdyStream3::zlib_destructor(void *opaque, void *addr) moz_free(addr); } +// This can be called N times.. 1 for syn_reply and 0->N for headers nsresult SpdyStream3::Uncompress(z_stream *context, char *blockStart, @@ -915,6 +1047,7 @@ SpdyStream3::Uncompress(z_stream *context, return NS_OK; } +// mDecompressBuffer contains 0 to N uncompressed Name/Value Header blocks nsresult SpdyStream3::FindHeader(nsCString name, nsDependentCSubstring &value) @@ -925,30 +1058,40 @@ SpdyStream3::FindHeader(nsCString name, (mDecompressBuffer.get()) + mDecompressBufferUsed; if (lastHeaderByte < nvpair) return NS_ERROR_ILLEGAL_VALUE; - uint32_t numPairs = - PR_ntohl(reinterpret_cast(mDecompressBuffer.get())[0]); - for (uint32_t index = 0; index < numPairs; ++index) { - if (lastHeaderByte < nvpair + 4) - return NS_ERROR_ILLEGAL_VALUE; - uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + - (nvpair[2] << 8) + nvpair[3]; - if (lastHeaderByte < nvpair + 4 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - nsDependentCSubstring nameString = - Substring(reinterpret_cast(nvpair) + 4, - reinterpret_cast(nvpair) + 4 + nameLen); - if (lastHeaderByte < nvpair + 8 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + - (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; - if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) - return NS_ERROR_ILLEGAL_VALUE; - if (nameString.Equals(name)) { - value.Assign(((char *)nvpair) + 8 + nameLen, valueLen); - return NS_OK; + + do { + uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); + + for (uint32_t index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) + return NS_ERROR_ILLEGAL_VALUE; + uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); + if (lastHeaderByte < nvpair + 8 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 8 + nameLen, valueLen); + return NS_OK; + } + + // that pair didn't match - try the next one in this block + nvpair += 8 + nameLen + valueLen; } - nvpair += 8 + nameLen + valueLen; - } + + // move to the next name/value header block (if there is one) - the + // first pair is offset 4 bytes into it + nvpair += 4; + } while (lastHeaderByte >= nvpair); + return NS_ERROR_NOT_AVAILABLE; } @@ -995,101 +1138,106 @@ SpdyStream3::ConvertHeaders(nsACString &aHeadersOut) (mDecompressBuffer.get()) + 4; const unsigned char *lastHeaderByte = reinterpret_cast (mDecompressBuffer.get()) + mDecompressBufferUsed; - if (lastHeaderByte < nvpair) return NS_ERROR_ILLEGAL_VALUE; - uint32_t numPairs = - PR_ntohl(reinterpret_cast(mDecompressBuffer.get())[0]); - - for (uint32_t index = 0; index < numPairs; ++index) { - if (lastHeaderByte < nvpair + 4) - return NS_ERROR_ILLEGAL_VALUE; - - uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + - (nvpair[2] << 8) + nvpair[3]; - if (lastHeaderByte < nvpair + 4 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - - nsDependentCSubstring nameString = - Substring(reinterpret_cast(nvpair) + 4, - reinterpret_cast(nvpair) + 4 + nameLen); - - if (lastHeaderByte < nvpair + 8 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - - // Look for illegal characters in the nameString. - // This includes upper case characters and nulls (as they will - // break the fixup-nulls-in-value-string algorithm) - // Look for upper case characters in the name. They are illegal. - for (char *cPtr = nameString.BeginWriting(); - cPtr && cPtr < nameString.EndWriting(); - ++cPtr) { - if (*cPtr <= 'Z' && *cPtr >= 'A') { - nsCString toLog(nameString); - - LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " - "upper case response header found. [%s]\n", - mSession, this, toLog.get())); + do { + uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); + for (uint32_t index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) return NS_ERROR_ILLEGAL_VALUE; - } - // check for null characters - if (*cPtr == '\0') + uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) return NS_ERROR_ILLEGAL_VALUE; - } - // HTTP Chunked responses are not legal over spdy. We do not need - // to look for chunked specifically because it is the only HTTP - // allowed default encoding and we did not negotiate further encodings - // via TE - if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { - LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " - "transfer-encoding found. Chunked is invalid and no TE sent.", - mSession, this)); + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); - return NS_ERROR_ILLEGAL_VALUE; - } + if (lastHeaderByte < nvpair + 8 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; - uint32_t valueLen = - (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + - (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; - - if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) - return NS_ERROR_ILLEGAL_VALUE; - - if (!nameString.Equals(NS_LITERAL_CSTRING(":version")) && - !nameString.Equals(NS_LITERAL_CSTRING(":status")) && - !nameString.Equals(NS_LITERAL_CSTRING("connection")) && - !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { - nsDependentCSubstring valueString = - Substring(reinterpret_cast(nvpair) + 8 + nameLen, - reinterpret_cast(nvpair) + 8 + nameLen + - valueLen); - - aHeadersOut.Append(nameString); - aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); - - // expand NULL bytes in the value string - for (char *cPtr = valueString.BeginWriting(); - cPtr && cPtr < valueString.EndWriting(); + // Look for illegal characters in the nameString. + // This includes upper case characters and nulls (as they will + // break the fixup-nulls-in-value-string algorithm) + // Look for upper case characters in the name. They are illegal. + for (char *cPtr = nameString.BeginWriting(); + cPtr && cPtr < nameString.EndWriting(); ++cPtr) { - if (*cPtr != 0) { - aHeadersOut.Append(*cPtr); - continue; + if (*cPtr <= 'Z' && *cPtr >= 'A') { + nsCString toLog(nameString); + + LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " + "upper case response header found. [%s]\n", + mSession, this, toLog.get())); + + return NS_ERROR_ILLEGAL_VALUE; } - // NULLs are really "\r\nhdr: " - aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); - aHeadersOut.Append(nameString); - aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + // check for null characters + if (*cPtr == '\0') + return NS_ERROR_ILLEGAL_VALUE; } - aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + // HTTP Chunked responses are not legal over spdy. We do not need + // to look for chunked specifically because it is the only HTTP + // allowed default encoding and we did not negotiate further encodings + // via TE + if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { + LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " + "transfer-encoding found. Chunked is invalid and no TE sent.", + mSession, this)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t valueLen = + (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + // spdy transport level headers shouldn't be gatewayed into http/1 + if (!nameString.IsEmpty() && nameString[0] != ':' && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring(reinterpret_cast(nvpair) + 8 + nameLen, + reinterpret_cast(nvpair) + 8 + nameLen + + valueLen); + + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + + // expand NULL bytes in the value string + for (char *cPtr = valueString.BeginWriting(); + cPtr && cPtr < valueString.EndWriting(); + ++cPtr) { + if (*cPtr != 0) { + aHeadersOut.Append(*cPtr); + continue; + } + + // NULLs are really "\r\nhdr: " + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + } + + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + } + // move to the next name/value pair in this block + nvpair += 8 + nameLen + valueLen; } - nvpair += 8 + nameLen + valueLen; - } + + // move to the next name/value header block (if there is one) - the + // first pair is offset 4 bytes into it + nvpair += 4; + } while (lastHeaderByte >= nvpair); aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 3\r\n\r\n")); LOG (("decoded response headers are:\n%s", @@ -1206,7 +1354,7 @@ SpdyStream3::OnReadSegment(const char *buf, LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d", this, *countRead, count, mSynFrameComplete)); if (mSynFrameComplete) { - MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment SynFrameComplete 0b"); + AdjustInitialWindow(); rv = TransmitFrame(nullptr, nullptr, true); if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // this can't happen @@ -1288,16 +1436,25 @@ SpdyStream3::OnReadSegment(const char *buf, nsresult SpdyStream3::OnWriteSegment(char *buf, - uint32_t count, - uint32_t *countWritten) + uint32_t count, + uint32_t *countWritten) { - LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x", - this, count, mUpstreamState)); + LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x 0x%X\n", + this, count, mUpstreamState, mStreamID)); MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); - MOZ_ASSERT(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + MOZ_ASSERT(mSegmentWriter); - return mSegmentWriter->OnWriteSegment(buf, count, countWritten); + if (!mPushSource) + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); + + nsresult rv; + rv = mPushSource->GetBufferedData(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + mSession->ConnectPushedStream(this); + return NS_OK; } } // namespace mozilla::net diff --git a/netwerk/protocol/http/SpdyStream3.h b/netwerk/protocol/http/SpdyStream3.h index 2b6c563251e1..38c2baa46c28 100644 --- a/netwerk/protocol/http/SpdyStream3.h +++ b/netwerk/protocol/http/SpdyStream3.h @@ -6,26 +6,28 @@ #ifndef mozilla_net_SpdyStream3_h #define mozilla_net_SpdyStream3_h -#include "nsAHttpTransaction.h" #include "mozilla/Attributes.h" +#include "nsAHttpTransaction.h" namespace mozilla { namespace net { -class SpdyStream3 MOZ_FINAL : public nsAHttpSegmentReader - , public nsAHttpSegmentWriter +class SpdyStream3 : public nsAHttpSegmentReader + , public nsAHttpSegmentWriter { public: NS_DECL_NSAHTTPSEGMENTREADER NS_DECL_NSAHTTPSEGMENTWRITER - SpdyStream3(nsAHttpTransaction *, - SpdySession3 *, nsISocketTransport *, - uint32_t, z_stream *, int32_t); + SpdyStream3(nsAHttpTransaction *, SpdySession3 *, int32_t); uint32_t StreamID() { return mStreamID; } + SpdyPushedStream3 *PushSource() { return mPushSource; } - nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); - nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); + virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); + virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); + virtual bool DeferCleanupOnSuccess() { return false; } + + const nsAFlatCString &Origin() const { return mOrigin; } bool RequestBlockedOnRead() { @@ -42,9 +44,10 @@ public: bool HasRegisteredID() { return mStreamID != 0; } - nsAHttpTransaction *Transaction() + nsAHttpTransaction *Transaction() { return mTransaction; } + virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { - return mTransaction; + return mTransaction ? mTransaction->LoadGroupConnectionInfo() : nullptr; } void Close(nsresult reason); @@ -81,15 +84,25 @@ public: } uint64_t LocalUnAcked() { return mLocalUnacked; } + int64_t LocalWindow() { return mLocalWindow; } + bool BlockedOnRwin() { return mBlockedOnRwin; } -private: + // A pull stream has an implicit sink, a pushed stream has a sink + // once it is matched to a pull stream. + virtual bool HasSink() { return true; } - // a SpdyStream3 object is only destroyed by being removed from the - // SpdySession3 mStreamTransactionHash - make the dtor private to - // just the AutoPtr implementation needed for that hash. - friend class nsAutoPtr; - ~SpdyStream3(); + virtual ~SpdyStream3(); + +protected: + nsresult FindHeader(nsCString, nsDependentCSubstring &); + + static void CreatePushHashKey(const nsCString &scheme, + const nsCString &hostHeader, + uint64_t serial, + const nsCSubstring &pathInfo, + nsCString &outOrigin, + nsCString &outKey); enum stateType { GENERATING_SYN_STREAM, @@ -99,12 +112,36 @@ private: UPSTREAM_COMPLETE }; + uint32_t mStreamID; + + // The session that this stream is a subset of + SpdySession3 *mSession; + + nsCString mOrigin; + + // Each stream goes from syn_stream to upstream_complete, perhaps + // looping on multiple instances of generating_request_body and + // sending_request_body for each SPDY chunk in the upload. + enum stateType mUpstreamState; + + // Flag is set when all http request headers have been read and ID is stable + uint32_t mSynFrameComplete : 1; + + // Flag is set when a FIN has been placed on a data or syn packet + // (i.e after the client has closed) + uint32_t mSentFinOnData : 1; + + void ChangeState(enum stateType); + +private: + friend class nsAutoPtr; + static PLDHashOperator hdrHashEnumerate(const nsACString &, nsAutoPtr &, void *); - void ChangeState(enum stateType); nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *); + void AdjustInitialWindow(); nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment); void GenerateDataFrameHeader(uint32_t, bool); @@ -114,12 +151,6 @@ private: void CompressToFrame(uint32_t); void CompressFlushFrame(); void ExecuteCompress(uint32_t); - nsresult FindHeader(nsCString, nsDependentCSubstring &); - - // Each stream goes from syn_stream to upstream_complete, perhaps - // looping on multiple instances of generating_request_body and - // sending_request_body for each SPDY chunk in the upload. - enum stateType mUpstreamState; // The underlying HTTP transaction. This pointer is used as the key // in the SpdySession3 mStreamTransactionHash so it is important to @@ -127,9 +158,6 @@ private: // (i.e. don't change it or release it after it is set in the ctor). nsRefPtr mTransaction; - // The session that this stream is a subset of - SpdySession3 *mSession; - // The underlying socket transport object is needed to propogate some events nsISocketTransport *mSocketTransport; @@ -139,23 +167,13 @@ private: nsAHttpSegmentReader *mSegmentReader; nsAHttpSegmentWriter *mSegmentWriter; - // The 24 bit SPDY stream ID - uint32_t mStreamID; - // The quanta upstream data frames are chopped into uint32_t mChunkSize; - // Flag is set when all http request headers have been read and ID is stable - uint32_t mSynFrameComplete : 1; - // Flag is set when the HTTP processor has more data to send // but has blocked in doing so. uint32_t mRequestBlockedOnRead : 1; - // Flag is set when a FIN has been placed on a data or syn packet - // (i.e after the client has closed) - uint32_t mSentFinOnData : 1; - // Flag is set after the response frame bearing the fin bit has // been processed. (i.e. after the server has closed). uint32_t mRecvdFin : 1; @@ -230,6 +248,9 @@ private: // For Progress Events uint64_t mTotalSent; uint64_t mTotalRead; + + // For SpdyPush + SpdyPushedStream3 *mPushSource; }; }} // namespace mozilla::net diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build index ae3e5d12bbfc..848a8ad31937 100644 --- a/netwerk/protocol/http/moz.build +++ b/netwerk/protocol/http/moz.build @@ -39,6 +39,7 @@ EXPORTS.mozilla.net += [ 'HttpChannelParent.h', 'HttpInfo.h', 'PHttpChannelParams.h', + 'PSpdyPush3.h', ] CPP_SOURCES += [ @@ -50,6 +51,7 @@ CPP_SOURCES += [ 'HttpChannelParentListener.cpp', 'HttpInfo.cpp', 'NullHttpTransaction.cpp', + 'SpdyPush3.cpp', 'SpdySession2.cpp', 'SpdySession3.cpp', 'SpdyStream2.cpp', diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h index a45bf322700a..647bdde6dd1a 100644 --- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -47,6 +47,10 @@ public: virtual nsresult ResumeSend() = 0; virtual nsresult ResumeRecv() = 0; + // called by a transaction to force a "read from network" iteration + // even if not scheduled by socket associated with connection + virtual nsresult ForceRecv() = 0; + // After a connection has had ResumeSend() called by a transaction, // and it is ready to write to the network it may need to know the // transaction that has data to write. This is only an issue for @@ -175,6 +179,12 @@ public: return NS_ERROR_FAILURE; \ return (fwdObject)->ResumeRecv(); \ } \ + nsresult ForceRecv() \ + { \ + if (!(fwdObject)) \ + return NS_ERROR_FAILURE; \ + return (fwdObject)->ForceRecv(); \ + } \ nsISocketTransport *Transport() \ { \ if (!(fwdObject)) \ diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index f93d87bb2860..7b62de0c5fb8 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -16,6 +16,8 @@ class nsIEventTarget; class nsITransport; class nsHttpRequestHead; class nsHttpPipeline; +class nsHttpTransaction; +class nsILoadGroupConnectionInfo; //---------------------------------------------------------------------------- // Abstract base class for a HTTP transaction: @@ -100,6 +102,12 @@ public: virtual nsresult SetPipelinePosition(int32_t) = 0; virtual int32_t PipelinePosition() = 0; + // Occasionally the abstract interface has to give way to base implementations + // to respect differences between spdy, pipelines, etc.. + // These Query* (and IsNUllTransaction()) functions provide a way to do + // that without using xpcom or rtti. Any calling code that can't deal with + // a null response from one of them probably shouldn't be using nsAHttpTransaction + // If we used rtti this would be the result of doing // dynamic_cast(this).. i.e. it can be nullptr for // non pipeline implementations of nsAHttpTransaction @@ -110,6 +118,9 @@ public: // its IO functions all the time. virtual bool IsNullTransaction() { return false; } + // return the load group connection information associated with the transaction + virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return nullptr; } + // Every transaction is classified into one of the types below. When using // HTTP pipelines, only transactions with the same type appear on the same // pipeline. @@ -177,7 +188,7 @@ public: // commitment now but might in the future and forceCommitment is not true . // (forceCommitment requires a hard failure or OK at this moment.) // - // Spdy uses this to make sure frames are atomic. + // SpdySession uses this to make sure frames are atomic. virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) { return NS_ERROR_FAILURE; diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index cdf790ad7984..1aa77276c63c 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -383,6 +383,13 @@ nsHttpConnection::SetupNPN(uint32_t caps) return; nsTArray protocolArray; + + // The first protocol is used as the fallback if none of the + // protocols supported overlap with the server's list. + // In the case of overlap, matching priority is driven by + // the order of the server's advertisement. + protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); + if (gHttpHandler->IsSpdyEnabled() && !(caps & NS_HTTP_DISALLOW_SPDY)) { LOG(("nsHttpConnection::SetupNPN Allow SPDY NPN selection")); @@ -394,7 +401,6 @@ nsHttpConnection::SetupNPN(uint32_t caps) gHttpHandler->SpdyInfo()->VersionString[1]); } - protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK")); mNPNComplete = false; @@ -1090,6 +1096,35 @@ nsHttpConnection::ResumeRecv() return NS_ERROR_UNEXPECTED; } + +class nsHttpConnectionForceRecv : public nsRunnable +{ +public: + nsHttpConnectionForceRecv(nsHttpConnection *aConn) + : mConn(aConn) {} + + NS_IMETHOD Run() + { + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (!mConn->mSocketIn) + return NS_OK; + return mConn->OnInputStreamReady(mConn->mSocketIn); + } +private: + nsRefPtr mConn; +}; + +// trigger an asynchronous read +nsresult +nsHttpConnection::ForceRecv() +{ + LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this)); + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + return NS_DispatchToCurrentThread(new nsHttpConnectionForceRecv(this)); +} + void nsHttpConnection::BeginIdleMonitoring() { diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index b63058653f47..89177c82c419 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -120,6 +120,9 @@ public: nsresult ResumeRecv(); int64_t MaxBytesRead() {return mMaxBytesRead;} + friend class nsHttpConnectionForceRecv; + nsresult ForceRecv(); + static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *, uint32_t, uint32_t, uint32_t *); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index 5178264c1025..05633992dd69 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -183,8 +183,10 @@ nsHttpHandler::nsHttpHandler() , mCoalesceSpdy(true) , mUseAlternateProtocol(false) , mSpdyPersistentSettings(false) + , mAllowSpdyPush(true) , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize) , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize) + , mSpdyPushAllowance(32768) , mSpdyPingThreshold(PR_SecondsToInterval(58)) , mSpdyPingTimeout(PR_SecondsToInterval(8)) , mConnectTimeout(90000) @@ -1159,6 +1161,22 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff)); } + if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"), + &cVar); + if (NS_SUCCEEDED(rv)) + mAllowSpdyPush = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.push-allowance"), &val); + if (NS_SUCCEEDED(rv)) { + mSpdyPushAllowance = + static_cast + (clamped(val, 1024, static_cast(ASpdySession::kInitialRwin))); + } + } + // The amount of seconds to wait for a spdy ping response before // closing the session. if (PREF_CHANGED(HTTP_PREF("spdy.send-buffer-size"))) { diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index be68164866d5..7dcb75489871 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -101,8 +101,10 @@ public: bool UseSpdyPersistentSettings() { return mSpdyPersistentSettings; } uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; } uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; } + uint32_t SpdyPushAllowance() { return mSpdyPushAllowance; } PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; } PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; } + bool AllowSpdyPush() { return mAllowSpdyPush; } uint32_t ConnectTimeout() { return mConnectTimeout; } uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; } bool CritialRequestPrioritization() { return mCritialRequestPrioritization; } @@ -416,8 +418,10 @@ private: bool mCoalesceSpdy; bool mUseAlternateProtocol; bool mSpdyPersistentSettings; + bool mAllowSpdyPush; uint32_t mSpdySendingChunkSize; uint32_t mSpdySendBufferSize; + uint32_t mSpdyPushAllowance; PRIntervalTime mSpdyPingThreshold; PRIntervalTime mSpdyPingTimeout; diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 6cc994291321..655eaf877582 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -115,8 +115,9 @@ public: const mozilla::TimeStamp GetPendingTime() { return mPendingTime; } bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; } - void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; } + // overload of nsAHttpTransaction::LoadGroupConnectionInfo() nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI.get(); } + void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; } void DispatchedAsBlocking(); void RemoveDispatchedAsBlocking(); diff --git a/netwerk/socket/nsISSLSocketControl.idl b/netwerk/socket/nsISSLSocketControl.idl index b01dbe70c1e5..258f207e6566 100644 --- a/netwerk/socket/nsISSLSocketControl.idl +++ b/netwerk/socket/nsISSLSocketControl.idl @@ -26,7 +26,9 @@ interface nsISSLSocketControl : nsISupports { tunnel during the SSL handshake. The NPNList is the list of offered client side protocols. setNPNList() needs to be called before any data is read or written (including the - handshake to be setup correctly. */ + handshake to be setup correctly. The server determines the + priority when multiple matches occur, but if there is no overlap + the first protocol in the list is used. */ [noscript] void setNPNList(in nsCStringTArrayRef aNPNList); diff --git a/netwerk/test/unit/test_spdy.js b/netwerk/test/unit/test_spdy.js index a9a5969753b2..e5f55bcf18ee 100644 --- a/netwerk/test/unit/test_spdy.js +++ b/netwerk/test/unit/test_spdy.js @@ -1,3 +1,5 @@ +// test spdy/3 + var Ci = Components.interfaces; var Cc = Components.classes; @@ -20,8 +22,7 @@ var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', function checkIsSpdy(request) { try { - if (request.getResponseHeader("X-Firefox-Spdy") == "2" || - request.getResponseHeader("X-Firefox-Spdy") == "3") { + if (request.getResponseHeader("X-Firefox-Spdy") == "3") { if (request.getResponseHeader("X-Connection-Spdy") == "yes") { return true; } @@ -125,6 +126,20 @@ SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, of read_stream(stream, cnt); }; +var SpdyPushListener = function() {}; + +SpdyPushListener.prototype = new SpdyCheckListener(); + +SpdyPushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + if (ctx.originalURI.spec == "https://localhost:4443/push.js" || + ctx.originalURI.spec == "https://localhost:4443/push2.js") { + do_check_eq(request.getResponseHeader("pushed"), "yes"); + } + read_stream(stream, cnt); +}; + // Does the appropriate checks for a large GET response var SpdyBigListener = function() {}; @@ -222,6 +237,34 @@ function test_spdy_header() { chan.asyncOpen(listener, null); } +function test_spdy_push1() { + var chan = makeChan("https://localhost:4443/push"); + chan.loadGroup = loadGroup; + var listener = new SpdyPushListener(); + chan.asyncOpen(listener, chan); +} + +function test_spdy_push2() { + var chan = makeChan("https://localhost:4443/push.js"); + chan.loadGroup = loadGroup; + var listener = new SpdyPushListener(); + chan.asyncOpen(listener, chan); +} + +function test_spdy_push3() { + var chan = makeChan("https://localhost:4443/push2"); + chan.loadGroup = loadGroup; + var listener = new SpdyPushListener(); + chan.asyncOpen(listener, chan); +} + +function test_spdy_push4() { + var chan = makeChan("https://localhost:4443/push2.js"); + chan.loadGroup = loadGroup; + var listener = new SpdyPushListener(); + chan.asyncOpen(listener, chan); +} + // Make sure we handle GETs that cover more than 2 frames properly function test_spdy_big() { var chan = makeChan("https://localhost:4443/big"); @@ -257,10 +300,17 @@ function test_spdy_post_big() { do_post(posts[1], chan, listener); } +// hack - the header test resets the multiplex object on the server, +// so make sure header is always run before the multiplex test. + var tests = [ test_spdy_basic + , test_spdy_push1 + , test_spdy_push2 + , test_spdy_push3 + , test_spdy_push4 , test_spdy_xhr - , test_spdy_multiplex , test_spdy_header + , test_spdy_multiplex , test_spdy_big , test_spdy_post , test_spdy_post_big @@ -328,6 +378,21 @@ function addCertOverride(host, port, bits) { } } +var prefs; +var spdypref; +var spdy2pref; +var spdy3pref; +var spdypush; + +var loadGroup; + +function resetPrefs() { + prefs.setBoolPref("network.http.spdy.enabled", spdypref); + prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref); + prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); + prefs.setBoolPref("network.http.spdy.allow-push", spdypush); +} + function run_test() { // Set to allow the cert presented by our SPDY server do_get_profile(); @@ -342,8 +407,17 @@ function run_test() { prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); - // Make sure spdy is enabled + // Enable all versions of spdy to see that we auto negotiate spdy/3 + spdypref = prefs.getBoolPref("network.http.spdy.enabled"); + spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2"); + spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); + spdypush = prefs.getBoolPref("network.http.spdy.allow-push"); prefs.setBoolPref("network.http.spdy.enabled", true); + prefs.setBoolPref("network.http.spdy.enabled.v2", true); + prefs.setBoolPref("network.http.spdy.enabled.v3", true); + prefs.setBoolPref("network.http.spdy.allow-push", true); + + loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup); // And make go! run_next_test(); diff --git a/netwerk/test/unit/test_spdy2.js b/netwerk/test/unit/test_spdy2.js new file mode 100644 index 000000000000..8833d1233b8c --- /dev/null +++ b/netwerk/test/unit/test_spdy2.js @@ -0,0 +1,372 @@ +// test spdy/2 + +var Ci = Components.interfaces; +var Cc = Components.classes; + +// Generate a small and a large post with known pre-calculated md5 sums +function generateContent(size) { + var content = ""; + for (var i = 0; i < size; i++) { + content += "0"; + } + return content; +} + +var posts = []; +posts.push(generateContent(10)); +posts.push(generateContent(128 * 1024)); + +// pre-calculated md5sums (in hex) of the above posts +var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', + '8f607cfdd2c87d6a7eedb657dafbd836']; + +function checkIsSpdy(request) { + try { + if (request.getResponseHeader("X-Firefox-Spdy") == "2") { + if (request.getResponseHeader("X-Connection-Spdy") == "yes") { + return true; + } + return false; // Weird case, but the server disagrees with us + } + } catch (e) { + // Nothing to do here + } + return false; +} + +var SpdyCheckListener = function() {}; + +SpdyCheckListener.prototype = { + onStartRequestFired: false, + onDataAvailableFired: false, + isSpdyConnection: false, + + onStartRequest: function testOnStartRequest(request, ctx) { + this.onStartRequestFired = true; + + if (!Components.isSuccessCode(request.status)) + do_throw("Channel should have a success code! (" + request.status + ")"); + if (!(request instanceof Components.interfaces.nsIHttpChannel)) + do_throw("Expecting an HTTP channel"); + + do_check_eq(request.responseStatus, 200); + do_check_eq(request.requestSucceeded, true); + }, + + onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + + read_stream(stream, cnt); + }, + + onStopRequest: function testOnStopRequest(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + + run_next_test(); + do_test_finished(); + } +}; + +/* + * Support for testing valid multiplexing of streams + */ + +var multiplexContent = generateContent(30*1024); +var completed_channels = []; +function register_completed_channel(listener) { + completed_channels.push(listener); + if (completed_channels.length == 2) { + do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); + run_next_test(); + do_test_finished(); + } +} + +/* Listener class to control the testing of multiplexing */ +var SpdyMultiplexListener = function() {}; + +SpdyMultiplexListener.prototype = new SpdyCheckListener(); + +SpdyMultiplexListener.prototype.streamID = 0; +SpdyMultiplexListener.prototype.buffer = ""; + +SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID")); + var data = read_stream(stream, cnt); + this.buffer = this.buffer.concat(data); +}; + +SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + do_check_true(this.buffer == multiplexContent); + + // This is what does most of the hard work for us + register_completed_channel(this); +}; + +// Does the appropriate checks for header gatewaying +var SpdyHeaderListener = function(value) { + this.value = value +}; + +SpdyHeaderListener.prototype = new SpdyCheckListener(); +SpdyHeaderListener.prototype.value = ""; + +SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value); + read_stream(stream, cnt); +}; + +// Does the appropriate checks for a large GET response +var SpdyBigListener = function() {}; + +SpdyBigListener.prototype = new SpdyCheckListener(); +SpdyBigListener.prototype.buffer = ""; + +SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + this.buffer = this.buffer.concat(read_stream(stream, cnt)); + // We know the server should send us the same data as our big post will be, + // so the md5 should be the same + do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5")); +}; + +SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + + // Don't want to flood output, so don't use do_check_eq + do_check_true(this.buffer == posts[1]); + + run_next_test(); + do_test_finished(); +}; + +// Does the appropriate checks for POSTs +var SpdyPostListener = function(expected_md5) { + this.expected_md5 = expected_md5; +}; + +SpdyPostListener.prototype = new SpdyCheckListener(); +SpdyPostListener.prototype.expected_md5 = ""; + +SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + read_stream(stream, cnt); + do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); +}; + +function makeChan(url) { + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); + + return chan; +} + +// Make sure we make a spdy connection and both us and the server mark it as such +function test_spdy_basic() { + var chan = makeChan("https://localhost:4443/"); + var listener = new SpdyCheckListener(); + chan.asyncOpen(listener, null); +} + +// Support for making sure XHR works over SPDY +function checkXhr(xhr) { + if (xhr.readyState != 4) { + return; + } + + do_check_eq(xhr.status, 200); + do_check_eq(checkIsSpdy(xhr), true); + run_next_test(); + do_test_finished(); +} + +// Fires off an XHR request over SPDY +function test_spdy_xhr() { + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + req.open("GET", "https://localhost:4443/", true); + req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, + false); + req.send(null); +} + +// Test to make sure we get multiplexing right +function test_spdy_multiplex() { + var chan1 = makeChan("https://localhost:4443/multiplex1"); + var chan2 = makeChan("https://localhost:4443/multiplex2"); + var listener1 = new SpdyMultiplexListener(); + var listener2 = new SpdyMultiplexListener(); + chan1.asyncOpen(listener1, null); + chan2.asyncOpen(listener2, null); +} + +// Test to make sure we gateway non-standard headers properly +function test_spdy_header() { + var chan = makeChan("https://localhost:4443/header"); + var hvalue = "Headers are fun"; + var listener = new SpdyHeaderListener(hvalue); + chan.setRequestHeader("X-Test-Header", hvalue, false); + chan.asyncOpen(listener, null); +} + +// Make sure we handle GETs that cover more than 2 frames properly +function test_spdy_big() { + var chan = makeChan("https://localhost:4443/big"); + var listener = new SpdyBigListener(); + chan.asyncOpen(listener, null); +} + +// Support for doing a POST +function do_post(content, chan, listener) { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.data = content; + + var uchan = chan.QueryInterface(Ci.nsIUploadChannel); + uchan.setUploadStream(stream, "text/plain", stream.available()); + + chan.requestMethod = "POST"; + + chan.asyncOpen(listener, null); +} + +// Make sure we can do a simple POST +function test_spdy_post() { + var chan = makeChan("https://localhost:4443/post"); + var listener = new SpdyPostListener(md5s[0]); + do_post(posts[0], chan, listener); +} + +// Make sure we can do a POST that covers more than 2 frames +function test_spdy_post_big() { + var chan = makeChan("https://localhost:4443/post"); + var listener = new SpdyPostListener(md5s[1]); + do_post(posts[1], chan, listener); +} + +// hack - the header test resets the multiplex object on the server, +// so make sure header is always run before the multiplex test. + +var tests = [ test_spdy_basic + , test_spdy_xhr + , test_spdy_header + , test_spdy_multiplex + , test_spdy_big + , test_spdy_post + , test_spdy_post_big + ]; +var current_test = 0; + +function run_next_test() { + if (current_test < tests.length) { + tests[current_test](); + current_test++; + do_test_pending(); + } +} + +// Support for making sure we can talk to the invalid cert the server presents +var CertOverrideListener = function(host, port, bits) { + this.host = host; + if (port) { + this.port = port; + } + this.bits = bits; +}; + +CertOverrideListener.prototype = { + host: null, + port: -1, + bits: null, + + getInterface: function(aIID) { + return this.QueryInterface(aIID); + }, + + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIBadCertListener2) || + aIID.equals(Ci.nsIInterfaceRequestor) || + aIID.equals(Ci.nsISupports)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + notifyCertProblem: function(socketInfo, sslStatus, targetHost) { + var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; + var cos = Cc["@mozilla.org/security/certoverride;1"]. + getService(Ci.nsICertOverrideService); + cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); + return true; + }, +}; + +function addCertOverride(host, port, bits) { + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + try { + var url; + if (port) { + url = "https://" + host + ":" + port + "/"; + } else { + url = "https://" + host + "/"; + } + req.open("GET", url, false); + req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); + req.send(null); + } catch (e) { + // This will fail since the server is not trusted yet + } +} + +var prefs; +var spdypref; +var spdy2pref; +var spdy3pref; + +function resetPrefs() { + prefs.setBoolPref("network.http.spdy.enabled", spdypref); + prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref); + prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); +} + +function run_test() { + // Set to allow the cert presented by our SPDY server + do_get_profile(); + prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit"); + prefs.setIntPref("network.http.speculative-parallel-limit", 0); + + addCertOverride("localhost", 4443, + Ci.nsICertOverrideService.ERROR_UNTRUSTED | + Ci.nsICertOverrideService.ERROR_MISMATCH | + Ci.nsICertOverrideService.ERROR_TIME); + + prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); + + // Make sure just spdy/2 is enabled + spdypref = prefs.getBoolPref("network.http.spdy.enabled"); + spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2"); + spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); + prefs.setBoolPref("network.http.spdy.enabled", true); + prefs.setBoolPref("network.http.spdy.enabled.v2", true); + prefs.setBoolPref("network.http.spdy.enabled.v3", false); + + do_register_cleanup(resetPrefs); + + // And make go! + run_next_test(); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 63dcfbc594d8..992136d9d75c 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -170,8 +170,10 @@ fail-if = os == "android" [test_socks.js] # Bug 675039: test fails consistently on Android fail-if = os == "android" -[test_spdy.js] # spdy unit tests require us to have node available to run the spdy server +[test_spdy2.js] +run-if = hasNode +[test_spdy.js] run-if = hasNode [test_speculative_connect.js] [test_standardurl.js] diff --git a/testing/config/Makefile.in b/testing/config/Makefile.in deleted file mode 100644 index da303aa73998..000000000000 --- a/testing/config/Makefile.in +++ /dev/null @@ -1,24 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk -include $(topsrcdir)/config/rules.mk - -CONFIG_FILES = \ - mozharness_config.py \ - $(NULL) - -_DEST_DIR = $(DEPTH)/_tests/config -libs:: $(CONFIG_FILES) - $(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR) - -stage-package: PKG_STAGE = $(DIST)/test-package-stage -stage-package: - $(NSINSTALL) -D $(PKG_STAGE)/config - @(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(CONFIG_FILES)) | (cd $(PKG_STAGE)/config && tar -xf -) diff --git a/testing/config/moz.build b/testing/config/moz.build deleted file mode 100644 index 58ce5e273390..000000000000 --- a/testing/config/moz.build +++ /dev/null @@ -1,5 +0,0 @@ -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - diff --git a/testing/marionette/client/marionette/tests/unit/test_multi_finger.py b/testing/marionette/client/marionette/tests/unit/test_multi_finger.py index 35778d803c97..96221d2f8788 100644 --- a/testing/marionette/client/marionette/tests/unit/test_multi_finger.py +++ b/testing/marionette/client/marionette/tests/unit/test_multi_finger.py @@ -21,7 +21,7 @@ class testMultiFinger(MarionetteTestCase): expected = "button1-touchstart" self.wait_for_condition(lambda m: m.execute_script("return document.getElementById('button1').innerHTML;") == expected) self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;")) - self.assertIn("button3-touchstart-touchend", self.marionette.execute_script("return document.getElementById('button3').innerHTML;")) + self.assertTrue("button3-touchstart-touchend" in self.marionette.execute_script("return document.getElementById('button3').innerHTML;")) def test_move_offset_element(self): testAction = self.marionette.absolute_url("testAction.html") @@ -37,7 +37,7 @@ class testMultiFinger(MarionetteTestCase): expected = "button1-touchstart" self.wait_for_condition(lambda m: m.execute_script("return document.getElementById('button1').innerHTML;") == expected) self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;")) - self.assertIn("button3-touchstart-touchend", self.marionette.execute_script("return document.getElementById('button3').innerHTML;")) + self.assertTrue("button3-touchstart-touchend" in self.marionette.execute_script("return document.getElementById('button3').innerHTML;")) def test_three_fingers(self): testAction = self.marionette.absolute_url("testAction.html") @@ -59,7 +59,7 @@ class testMultiFinger(MarionetteTestCase): self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;")) button3_text = self.marionette.execute_script("return document.getElementById('button3').innerHTML;") button4_text = self.marionette.execute_script("return document.getElementById('button4').innerHTML;") - self.assertIn("button3-touchstart-touchend", button3_text) - self.assertIn("button4-touchstart-touchend", button4_text) + self.assertTrue("button3-touchstart-touchend" in button3_text) + self.assertTrue("button4-touchstart-touchend" in button4_text) self.assertTrue(int(button3_text.rsplit("-")[-1]) >= 5000) self.assertTrue(int(button4_text.rsplit("-")[-1]) >= 5000) diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index 5a0bec8052aa..9b1852e2040d 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -594,10 +594,31 @@ function coordinates(target, x, y) { */ function elementInViewport(el) { let rect = el.getBoundingClientRect(); - return (rect.top >= curWindow.pageYOffset && - rect.left >= curWindow.pageXOffset && - rect.bottom <= (curWindow.pageYOffset + curWindow.innerHeight) && - rect.right <= (curWindow.pageXOffset + curWindow.innerWidth) + return (/* Top left corner is in view */ + (rect.top >= curWindow.pageYOffset && + rect.top <= (curWindow.pageYOffset + curWindow.innerHeight) && + rect.left >= curWindow.pageXOffset && + rect.left <= (curWindow.pageXOffset + curWindow.innerWidth)) || + /* Top right corner is in view */ + (rect.top >= curWindow.pageYOffset && + rect.top <= (curWindow.pageYOffset + curWindow.innerHeight) && + rect.right >= curWindow.pageXOffset && + rect.right <= (curWindow.pageXOffset + curWindow.innerWidth)) || + /* Bottom right corner is in view */ + (rect.bottom >= curWindow.pageYOffset && + rect.bottom <= (curWindow.pageYOffset + curWindow.innerHeight) && + rect.right >= curWindow.pageXOffset && + rect.right <= (curWindow.pageXOffset + curWindow.innerWidth)) || + /* Bottom left corner is in view */ + (rect.bottom >= curWindow.pageYOffset && + rect.bottom <= (curWindow.pageYOffset + curWindow.innerHeight) && + rect.left >= curWindow.pageXOffset && + rect.left <= (curWindow.pageXOffset + curWindow.innerWidth)) || + /* Center of the element is in view if element larger than viewport */ + ((rect.top + (rect.height/2)) <= curWindow.pageYOffset && + (rect.top + (rect.height/2)) >= (curWindow.pageYOffset + curWindow.innerHeight) && + (rect.left + (rect.width/2)) <= curWindow.pageXOffset && + (rect.left + (rect.width/2)) >= (curWindow.pageXOffset + curWindow.innerWidth)) ); } diff --git a/testing/mozbase/mozfile/mozfile/mozfile.py b/testing/mozbase/mozfile/mozfile/mozfile.py index ee232444bdb1..582db2218651 100644 --- a/testing/mozbase/mozfile/mozfile/mozfile.py +++ b/testing/mozbase/mozfile/mozfile/mozfile.py @@ -6,9 +6,16 @@ import os import tarfile import tempfile import urlparse +import urllib2 import zipfile -__all__ = ['extract_tarball', 'extract_zip', 'extract', 'is_url', 'rmtree', 'NamedTemporaryFile'] +__all__ = ['extract_tarball', + 'extract_zip', + 'extract', + 'is_url', + 'load', + 'rmtree', + 'NamedTemporaryFile'] ### utilities for extracting archives @@ -50,6 +57,8 @@ def extract_zip(src, dest): _dest = open(filename, 'wb') _dest.write(bundle.read(name)) _dest.close() + mode = bundle.getinfo(name).external_attr >> 16 & 0x1FF + os.chmod(filename, mode) bundle.close() return namelist @@ -64,12 +73,12 @@ def extract(src, dest=None): """ assert os.path.exists(src), "'%s' does not exist" % src - assert not os.path.isfile(dest), "dest cannot be a file" if dest is None: dest = os.path.dirname(src) elif not os.path.isdir(dest): os.makedirs(dest) + assert not os.path.isfile(dest), "dest cannot be a file" if zipfile.is_zipfile(src): namelist = extract_zip(src, dest) @@ -160,35 +169,42 @@ class NamedTemporaryFile(object): see https://bugzilla.mozilla.org/show_bug.cgi?id=821362 """ def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='tmp', - dir=None): + dir=None, delete=True): fd, path = tempfile.mkstemp(suffix, prefix, dir, 't' in mode) os.close(fd) self.file = open(path, mode) self._path = path + self._delete = delete self._unlinked = False def __getattr__(self, k): return getattr(self.__dict__['file'], k) + def __iter__(self): + return self.__dict__['file'] + def __enter__(self): self.file.__enter__() return self def __exit__(self, exc, value, tb): self.file.__exit__(exc, value, tb) - os.unlink(self.__dict__['_path']) - self._unlinked = True + if self.__dict__['_delete']: + os.unlink(self.__dict__['_path']) + self._unlinked = True def __del__(self): if self.__dict__['_unlinked']: return - self.file.__exit__(None, None, None) - os.unlink(self.__dict__['_path']) + if self.__dict__['_delete']: + os.unlink(self.__dict__['_path']) +### utilities dealing with URLs + def is_url(thing): """ Return True if thing looks like a URL. @@ -199,3 +215,20 @@ def is_url(thing): return len(parsed.scheme) >= 2 else: return len(parsed[0]) >= 2 + +def load(resource): + """ + open a file or URL for reading. If the passed resource string is not a URL, + or begins with 'file://', return a ``file``. Otherwise, return the + result of urllib2.urlopen() + """ + + # handle file URLs separately due to python stdlib limitations + if resource.startswith('file://'): + resource = resource[len('file://'):] + + if not is_url(resource): + # if no scheme is given, it is a file path + return file(resource) + + return urllib2.urlopen(resource) diff --git a/testing/mozbase/mozfile/setup.py b/testing/mozbase/mozfile/setup.py index 97dfe7dbb655..6689995452cb 100644 --- a/testing/mozbase/mozfile/setup.py +++ b/testing/mozbase/mozfile/setup.py @@ -4,7 +4,7 @@ from setuptools import setup -PACKAGE_VERSION = '0.3' +PACKAGE_VERSION = '0.7' setup(name='mozfile', version=PACKAGE_VERSION, @@ -19,5 +19,6 @@ setup(name='mozfile', packages=['mozfile'], include_package_data=True, zip_safe=False, - install_requires=[] + install_requires=[], + tests_require=['mozhttpd'] ) diff --git a/testing/mozbase/mozfile/tests/manifest.ini b/testing/mozbase/mozfile/tests/manifest.ini index cb9111015c12..008b38bfb1a4 100644 --- a/testing/mozbase/mozfile/tests/manifest.ini +++ b/testing/mozbase/mozfile/tests/manifest.ini @@ -1,2 +1,4 @@ [test.py] -[is_url.py] \ No newline at end of file +[test_load.py] +[test_tempfile.py] +[test_url.py] diff --git a/testing/mozbase/mozfile/tests/test_load.py b/testing/mozbase/mozfile/tests/test_load.py new file mode 100755 index 000000000000..d1d6d62d691d --- /dev/null +++ b/testing/mozbase/mozfile/tests/test_load.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +""" +tests for mozfile.load +""" + +import mozhttpd +import os +import tempfile +import unittest +from mozfile import load + +class TestLoad(unittest.TestCase): + """test the load function""" + + def test_http(self): + """test with mozhttpd and a http:// URL""" + + def example(request): + """example request handler""" + body = 'example' + return (200, {'Content-type': 'text/plain', + 'Content-length': len(body) + }, body) + + host = '127.0.0.1' + httpd = mozhttpd.MozHttpd(host=host, + port=8888, + urlhandlers=[{'method': 'GET', + 'path': '.*', + 'function': example}]) + try: + httpd.start(block=False) + content = load('http://127.0.0.1:8888/foo').read() + self.assertEqual(content, 'example') + finally: + httpd.stop() + + + def test_file_path(self): + """test loading from file path""" + try: + # create a temporary file + tmp = tempfile.NamedTemporaryFile(delete=False) + tmp.write('foo bar') + tmp.close() + + # read the file + contents = file(tmp.name).read() + self.assertEqual(contents, 'foo bar') + + # read the file with load and a file path + self.assertEqual(load(tmp.name).read(), contents) + + # read the file with load and a file URL + self.assertEqual(load('file://%s' % tmp.name).read(), contents) + finally: + # remove the tempfile + if os.path.exists(tmp.name): + os.remove(tmp.name) + +if __name__ == '__main__': + unittest.main() diff --git a/testing/mozbase/mozfile/tests/test_tempfile.py b/testing/mozbase/mozfile/tests/test_tempfile.py new file mode 100644 index 000000000000..5cabd89041fb --- /dev/null +++ b/testing/mozbase/mozfile/tests/test_tempfile.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +tests for mozfile.NamedTemporaryFile +""" + +import mozfile +import os +import unittest + +class TestNamedTemporaryFile(unittest.TestCase): + + def test_iteration(self): + """ensure the line iterator works""" + + # make a file and write to it + tf = mozfile.NamedTemporaryFile() + notes = ['doe', 'rae', 'mi'] + for note in notes: + tf.write('%s\n' % note) + tf.flush() + + # now read from it + tf.seek(0) + lines = [line.rstrip('\n') for line in tf.readlines()] + self.assertEqual(lines, notes) + + # now read from it iteratively + lines = [] + for line in tf: + lines.append(line.strip()) + self.assertEqual(lines, []) # because we did not seek(0) + tf.seek(0) + lines = [] + for line in tf: + lines.append(line.strip()) + self.assertEqual(lines, notes) + + def test_delete(self): + """ensure ``delete=True/False`` works as expected""" + + # make a deleteable file; ensure it gets cleaned up + path = None + with mozfile.NamedTemporaryFile(delete=True) as tf: + path = tf.name + self.assertTrue(isinstance(path, basestring)) + self.assertFalse(os.path.exists(path)) + + # it is also deleted when __del__ is called + # here we will do so explicitly + tf = mozfile.NamedTemporaryFile(delete=True) + path = tf.name + self.assertTrue(os.path.exists(path)) + del tf + self.assertFalse(os.path.exists(path)) + + # Now the same thing but we won't delete the file + path = None + try: + with mozfile.NamedTemporaryFile(delete=False) as tf: + path = tf.name + self.assertTrue(os.path.exists(path)) + finally: + if path and os.path.exists(path): + os.remove(path) + + path = None + try: + tf = mozfile.NamedTemporaryFile(delete=False) + path = tf.name + self.assertTrue(os.path.exists(path)) + del tf + self.assertTrue(os.path.exists(path)) + finally: + if path and os.path.exists(path): + os.remove(path) + +if __name__ == '__main__': + unittest.main() diff --git a/testing/mozbase/mozfile/tests/is_url.py b/testing/mozbase/mozfile/tests/test_url.py old mode 100644 new mode 100755 similarity index 100% rename from testing/mozbase/mozfile/tests/is_url.py rename to testing/mozbase/mozfile/tests/test_url.py diff --git a/testing/mozbase/mozprocess/mozprocess/processhandler.py b/testing/mozbase/mozprocess/mozprocess/processhandler.py index 7e7cf6dda978..509010ef073d 100644 --- a/testing/mozbase/mozprocess/mozprocess/processhandler.py +++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py @@ -633,7 +633,14 @@ falling back to not using job objects for managing child processes""" Note that this does not manage any state, save any output etc, it immediately kills the process. """ - return self.proc.kill() + try: + return self.proc.kill() + except AttributeError: + # Try to print a relevant error message. + if not self.proc: + print >> sys.stderr, "Unable to kill Process because call to ProcessHandler constructor failed." + else: + raise def readWithTimeout(self, f, timeout): """ diff --git a/testing/mozbase/mozprocess/setup.py b/testing/mozbase/mozprocess/setup.py index 6b1e1290a5be..83de82b0ed47 100644 --- a/testing/mozbase/mozprocess/setup.py +++ b/testing/mozbase/mozprocess/setup.py @@ -4,7 +4,7 @@ from setuptools import setup -PACKAGE_VERSION = '0.9' +PACKAGE_VERSION = '0.10' setup(name='mozprocess', version=PACKAGE_VERSION, diff --git a/testing/mozbase/mozprocess/tests/Makefile b/testing/mozbase/mozprocess/tests/Makefile index 1a9c92c07037..9f3a0c357b52 100644 --- a/testing/mozbase/mozprocess/tests/Makefile +++ b/testing/mozbase/mozprocess/tests/Makefile @@ -1,34 +1,38 @@ # -# proclaunch tests Makefile +# mozprocess proclaunch tests Makefile # -UNAME := $(shell uname -s) -ifeq ($(UNAME), MINGW32_NT-6.1) -WIN32 = 1 -endif -ifeq ($(UNAME), MINGW32_NT-5.1) -WIN32 = 1 -endif + +# include rules for platform determination +include iniparser/platform.mk ifeq ($(WIN32), 1) +# Win 32 CC = cl LINK = link CFLAGS = //Od //I "iniparser" //D "WIN32" //D "_WIN32" //D "_DEBUG" //D "_CONSOLE" //D "_UNICODE" //D "UNICODE" //Gm //EHsc //RTC1 //MDd //W3 //nologo //c //ZI //TC -LFLAGS = //OUT:"proclaunch.exe" //INCREMENTAL //LIBPATH:"iniparser\\" //NOLOGO //DEBUG //SUBSYSTEM:CONSOLE //DYNAMICBASE //NXCOMPAT //MACHINE:X86 //ERRORREPORT:PROMPT iniparser.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib +LFLAGS = //OUT:"proclaunch.exe" //INCREMENTAL //LIBPATH:"iniparser\\" //NOLOGO //DEBUG //SUBSYSTEM:CONSOLE //DYNAMICBASE //NXCOMPAT //ERRORREPORT:PROMPT iniparser.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib RM = rm -f -default: all all: iniparser proclaunch iniparser: $(MAKE) -C iniparser proclaunch.obj: proclaunch.c + @(echo "compiling proclaunch; platform: $(UNAME), WIN32: $(WIN32)") $(CC) $(CFLAGS) proclaunch.c proclaunch: proclaunch.obj $(LINK) $(LFLAGS) proclaunch.obj +clean: + $(RM) proclaunch.exe projloaunch.obj else +# *nix/Mac +LFLAGS = -L.. -liniparser +AR = ar +ARFLAGS = rcv +RM = rm -f CC = gcc ifeq ($(UNAME), Linux) CFLAGS = -g -v -Iiniparser @@ -36,22 +40,16 @@ else CFLAGS = -g -v -arch i386 -Iiniparser endif -LFLAGS = -L.. -liniparser -AR = ar -ARFLAGS = rcv -RM = rm -f - - -default: all - all: libiniparser.a proclaunch libiniparser.a: $(MAKE) -C iniparser proclaunch: proclaunch.c + @(echo "compiling proclaunch; platform: $(UNAME), WIN32: $(WIN32)") $(CC) $(CFLAGS) -o proclaunch proclaunch.c -Iiniparser -Liniparser -liniparser -clean veryclean: - $(RM) proclaunch +clean: + $(RM) proclaunch + $(MAKE) -C iniparser clean endif diff --git a/testing/mozbase/mozprocess/tests/iniparser/Makefile b/testing/mozbase/mozprocess/tests/iniparser/Makefile index ca6b6a243140..a6b443c8cfd8 100644 --- a/testing/mozbase/mozprocess/tests/iniparser/Makefile +++ b/testing/mozbase/mozprocess/tests/iniparser/Makefile @@ -1,18 +1,18 @@ # # iniparser Makefile # -UNAME := $(shell uname -s) -ifeq ($(UNAME), MINGW32_NT-6.1) -WIN32 = 1 -endif -ifeq ($(UNAME), MINGW32_NT-5.1) -WIN32 = 1 -endif +# source files +SRCS = iniparser.c \ + dictionary.c + +# include rules for platform determination +include platform.mk + +# flags for the the various systems ifeq ($(UNAME), Linux) # Compiler settings CC = gcc - # Ar settings to build the library AR = ar ARFLAGS = rcv SHLD = ${CC} ${CFLAGS} @@ -20,79 +20,57 @@ ifeq ($(UNAME), Linux) LDSHFLAGS = -shared -Wl,-Bsymbolic -Wl,-rpath -Wl,/usr/lib -Wl,-rpath,/usr/lib LDFLAGS = -Wl,-rpath -Wl,/usr/lib -Wl,-rpath,/usr/lib endif - ifeq ($(UNAME), Darwin) # Compiler settings CC = gcc # Ar settings to build the library AR = ar ARFLAGS = rcv - #SHLD = ${CC} ${CFLAGS} SHLD = libtool CFLAGS = -v -arch i386 -isysroot /Developer/SDKs/MacOSX10.6.sdk -fPIC -Wall -ansi -pedantic LDFLAGS = -arch_only i386 endif - ifeq ($(WIN32), 1) CC = cl CFLAGS = //Od //D "_WIN32" //D "WIN32" //D "_CONSOLE" //D "_CRT_SECURE_NO_WARNINGS" //D "_UNICODE" //D "UNICODE" //Gm //EHsc //RTC1 //MDd //W3 //nologo //c //ZI //TC LDFLAGS = //OUT:"iniparser.lib" //NOLOGO LINK = lib + RM = rm -f endif - + +# windows build rules ifeq ($(WIN32), 1) -SUFFIXES = .obj .c .h .lib -COMPILE.c=$(CC) $(CFLAGS) -c - -#.c.obj: -# @(echo "compiling $< ...") -# @($(COMPILE.c) $@ $<) +COMPILE.c = $(CC) $(CFLAGS) -c +OBJS = $(SRCS:.c=.obj) all: iniparser.obj dictionary.obj iniparser.lib -SRCS = iniparser.c \ - dictionary.c -OBJS = $(SRCS:.c=.obj) - iniparser.obj: dictionary.obj @($(CC) $(CFLAGS) iniparser.c) dictionary.obj: + @(echo "compiling dictionary; WIN32: $(WIN32); platform: $(UNAME)") @($(CC) $(CFLAGS) dictionary.c) - + iniparser.lib: dictionary.obj iniparser.obj @(echo "linking $(OBJS)") @($(LINK) $(LDFLAGS) $(OBJS)) - else -# Set RANLIB to ranlib on systems that require it (Sun OS < 4, Mac OSX) -# RANLIB = ranlib -RANLIB = true - -RM = rm -f - -# Implicit rules - -SUFFIXES = .o .c .h .a .so .sl - -COMPILE.c=$(CC) $(CFLAGS) -c -.c.o: - @(echo "compiling $< ...") - @($(COMPILE.c) -o $@ $<) - - -SRCS = iniparser.c \ - dictionary.c +# *nix (and Mac) build rules +RM = rm -f +COMPILE.c = $(CC) $(CFLAGS) -c OBJS = $(SRCS:.c=.o) +all: libiniparser.a libiniparser.so -default: libiniparser.a libiniparser.so +.c.o: + @(echo "platform: $(UNAME), WIN32=$(WIN32); compiling $< ...") + @($(COMPILE.c) -o $@ $<) libiniparser.a: $(OBJS) @($(AR) $(ARFLAGS) libiniparser.a $(OBJS)) - @($(RANLIB) libiniparser.a) ifeq ($(UNAME), Linux) libiniparser.so: $(OBJS) @@ -104,15 +82,4 @@ endif endif clean: - $(RM) $(OBJS) - -veryclean: - $(RM) $(OBJS) libiniparser.a libiniparser.so* - rm -rf ./html ; mkdir html - cd test ; $(MAKE) veryclean - -docs: - @(cd doc ; $(MAKE)) - -check: - @(cd test ; $(MAKE)) + $(RM) $(OBJS) libiniparser.* diff --git a/testing/mozbase/mozprocess/tests/iniparser/platform.mk b/testing/mozbase/mozprocess/tests/iniparser/platform.mk new file mode 100644 index 000000000000..bff0296fe1d6 --- /dev/null +++ b/testing/mozbase/mozprocess/tests/iniparser/platform.mk @@ -0,0 +1,8 @@ +# System platform + +# determine if windows +WIN32 := 0 +UNAME := $(shell uname -s) +ifneq (,$(findstring MINGW32_NT,$(UNAME))) +WIN32 = 1 +endif diff --git a/testing/mozbase/mozprocess/tests/manifest.ini b/testing/mozbase/mozprocess/tests/manifest.ini index e8bd19fa9bcf..3fdadf47567e 100644 --- a/testing/mozbase/mozprocess/tests/manifest.ini +++ b/testing/mozbase/mozprocess/tests/manifest.ini @@ -1 +1,5 @@ +# does not currently work on windows +# see https://bugzilla.mozilla.org/show_bug.cgi?id=790765#c51 + [test_mozprocess.py] +skip-if = os == 'win' \ No newline at end of file diff --git a/testing/mozbase/mozprocess/tests/test_mozprocess.py b/testing/mozbase/mozprocess/tests/test_mozprocess.py index 9ddc1bcc3519..7b5659890a85 100644 --- a/testing/mozbase/mozprocess/tests/test_mozprocess.py +++ b/testing/mozbase/mozprocess/tests/test_mozprocess.py @@ -4,13 +4,13 @@ # 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/. +import optparse import os import subprocess import sys import unittest -from time import sleep - from mozprocess import processhandler +from time import sleep here = os.path.dirname(os.path.abspath(__file__)) @@ -22,14 +22,33 @@ def make_proclaunch(aDir): Returns: the path to the proclaunch executable that is generated """ - # Ideally make should take care of this, but since it doesn't, - # on windows anyway, let's just call out both targets explicitly. - p = subprocess.call(["make", "-C", "iniparser"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=aDir) - p = subprocess.call(["make"],stdout=subprocess.PIPE, stderr=subprocess.PIPE ,cwd=aDir) + if sys.platform == "win32": exepath = os.path.join(aDir, "proclaunch.exe") else: exepath = os.path.join(aDir, "proclaunch") + + # remove the launcher, if it already exists + # otherwise, if the make fails you may not notice + if os.path.exists(exepath): + os.remove(exepath) + + # Ideally make should take care of both calls through recursion, but since it doesn't, + # on windows anyway (to file?), let's just call out both targets explicitly. + for command in [["make", "-C", "iniparser"], + ["make"]]: + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=aDir) + stdout, stderr = process.communicate() + if process.returncode: + # SomethingBadHappen; print all the things + print "%s: exit %d" % (command, process.returncode) + print "stdout:\n%s" % stdout + print "stderr:\n%s" % stderr + raise subprocess.CalledProcessError(process.returncode, command, stdout) + + # ensure the launcher now exists + if not os.path.exists(exepath): + raise AssertionError("proclaunch executable '%s' does not exist (sys.platform=%s)" % (exepath, sys.platform)) return exepath def check_for_process(processName): @@ -72,12 +91,18 @@ def check_for_process(processName): class ProcTest(unittest.TestCase): + # whether to remove created files on exit + cleanup = os.environ.get('CLEANUP', 'true').lower() in ('1', 'true') + @classmethod def setUpClass(cls): cls.proclaunch = make_proclaunch(here) @classmethod def tearDownClass(cls): + del cls.proclaunch + if not cls.cleanup: + return files = [('proclaunch',), ('proclaunch.exe',), ('iniparser', 'dictionary.o'), @@ -96,7 +121,6 @@ class ProcTest(unittest.TestCase): errors.append(str(e)) if errors: raise OSError("Error(s) encountered tearing down %s.%s:\n%s" % (cls.__module__, cls.__name__, '\n'.join(errors))) - del cls.proclaunch def test_process_normal_finish(self): """Process is started, runs to completion while we wait for it""" diff --git a/testing/mozbase/mozprofile/mozprofile/addons.py b/testing/mozbase/mozprofile/mozprofile/addons.py index dcd5df33e70e..8dbdf3cf197b 100644 --- a/testing/mozbase/mozprofile/mozprofile/addons.py +++ b/testing/mozbase/mozprofile/mozprofile/addons.py @@ -257,7 +257,7 @@ class AddonManager(object): extensions_path = os.path.join(self.profile, 'extensions', 'staged') for backup in os.listdir(self.backup_dir): backup_path = os.path.join(self.backup_dir, backup) - addon_path = os.path.join(extensions_path, addon) + addon_path = os.path.join(extensions_path, backup) shutil.move(backup_path, addon_path) if not os.listdir(self.backup_dir): shutil.rmtree(self.backup_dir, ignore_errors=True) diff --git a/testing/mozbase/mozprofile/mozprofile/permissions.py b/testing/mozbase/mozprofile/mozprofile/permissions.py index 31b241d3c2b4..197314aff2db 100644 --- a/testing/mozbase/mozprofile/mozprofile/permissions.py +++ b/testing/mozbase/mozprofile/mozprofile/permissions.py @@ -24,7 +24,7 @@ import urlparse # http://hg.mozilla.org/mozilla-central/file/b871dfb2186f/build/automation.py.in#l28 _DEFAULT_PORTS = { 'http': '8888', 'https': '4443', - 'ws': '9988', + 'ws': '4443', 'wss': '4443' } class LocationError(Exception): @@ -209,8 +209,6 @@ class ServerLocations(object): class Permissions(object): """Allows handling of permissions for ``mozprofile``""" - _num_permissions = 0 - def __init__(self, profileDir, locations=None): self._profileDir = profileDir self._locations = ServerLocations(add_callback=self.write_db) @@ -248,7 +246,6 @@ class Permissions(object): # set the permissions permissions = { 'allowXULXBL': 'noxul' not in location.options } for perm, allow in permissions.iteritems(): - self._num_permissions += 1 if allow: permission_type = 1 else: @@ -259,15 +256,14 @@ class Permissions(object): # if the db contains 8 columns, we're using user_version 3 if count == 8: - statement = "INSERT INTO moz_hosts values(?, ?, ?, ?, 0, 0, 0, 0)" + statement = "INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0)" cursor.execute("PRAGMA user_version=3;") else: - statement = "INSERT INTO moz_hosts values(?, ?, ?, ?, 0, 0)" + statement = "INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0)" cursor.execute("PRAGMA user_version=2;") cursor.execute(statement, - (self._num_permissions, location.host, perm, - permission_type)) + (location.host, perm, permission_type)) # Commit and close permDB.commit() diff --git a/testing/mozbase/mozprofile/mozprofile/prefs.py b/testing/mozbase/mozprofile/mozprofile/prefs.py index 81cd3b41d7da..e026f58baf3d 100644 --- a/testing/mozbase/mozprofile/mozprofile/prefs.py +++ b/testing/mozbase/mozprofile/mozprofile/prefs.py @@ -8,6 +8,7 @@ user preferences __all__ = ('PreferencesReadError', 'Preferences') +import mozfile import os import re import tokenize @@ -91,7 +92,7 @@ class Preferences(object): # section of INI file path, section = path.rsplit(':', 1) - if not os.path.exists(path): + if not os.path.exists(path) and not mozfile.is_url(path): raise PreferencesReadError("'%s' does not exist" % path) if section: @@ -120,7 +121,7 @@ class Preferences(object): """read preferences from an .ini file""" parser = ConfigParser() - parser.read(path) + parser.readfp(mozfile.load(path)) if section: if section not in parser.sections(): @@ -136,7 +137,7 @@ class Preferences(object): def read_json(cls, path): """read preferences from a JSON blob""" - prefs = json.loads(file(path).read()) + prefs = json.loads(mozfile.load(path).read()) if type(prefs) not in [list, dict]: raise PreferencesReadError("Malformed preferences: %s" % path) @@ -161,7 +162,7 @@ class Preferences(object): comment = re.compile('/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/', re.MULTILINE) marker = '##//' # magical marker - lines = [i.strip() for i in file(path).readlines() if i.strip()] + lines = [i.strip() for i in mozfile.load(path).readlines() if i.strip()] _lines = [] for line in lines: if line.startswith(('#', '//')): diff --git a/testing/mozbase/mozprofile/setup.py b/testing/mozbase/mozprofile/setup.py index 01e37fd10ba2..1fa41651a3cd 100644 --- a/testing/mozbase/mozprofile/setup.py +++ b/testing/mozbase/mozprofile/setup.py @@ -5,12 +5,13 @@ import sys from setuptools import setup -PACKAGE_VERSION = '0.7' +PACKAGE_VERSION = '0.9' # we only support python 2 right now assert sys.version_info[0] == 2 -deps = ["ManifestDestiny >= 0.5.4"] +deps = ["ManifestDestiny >= 0.5.4", + "mozfile >= 0.6"] # version-dependent dependencies try: import json @@ -37,12 +38,13 @@ setup(name='mozprofile', keywords='mozilla', author='Mozilla Automation and Tools team', author_email='tools@lists.mozilla.org', - url='https://wiki.mozilla.org/Auto-tools/Projects/MozBase', + url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase', license='MPL 2.0', packages=['mozprofile'], include_package_data=True, zip_safe=False, install_requires=deps, + tests_require=['mozhttpd', 'mozfile'], entry_points=""" # -*- Entry points: -*- [console_scripts] diff --git a/testing/mozbase/mozprofile/tests/addons/empty.xpi b/testing/mozbase/mozprofile/tests/addons/empty.xpi new file mode 100644 index 000000000000..26f28f099d24 Binary files /dev/null and b/testing/mozbase/mozprofile/tests/addons/empty.xpi differ diff --git a/testing/mozbase/mozprofile/tests/bug785146.py b/testing/mozbase/mozprofile/tests/bug785146.py old mode 100644 new mode 100755 index 29376d8d17fd..4b9f0e082af7 --- a/testing/mozbase/mozprofile/tests/bug785146.py +++ b/testing/mozbase/mozprofile/tests/bug785146.py @@ -4,15 +4,17 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +import mozfile import os import shutil +import tempfile +import unittest +from mozprofile.permissions import Permissions try: import sqlite3 except ImportError: from pysqlite2 import dbapi2 as sqlite3 -import tempfile -import unittest -from mozprofile.permissions import Permissions + class PermissionsTest(unittest.TestCase): @@ -21,12 +23,9 @@ http://127.0.0.1:80 noxul http://127.0.0.1:8888 privileged """ - profile_dir = None - locations_file = None - def setUp(self): self.profile_dir = tempfile.mkdtemp() - self.locations_file = tempfile.NamedTemporaryFile() + self.locations_file = mozfile.NamedTemporaryFile() self.locations_file.write(self.locations) self.locations_file.flush() diff --git a/testing/mozbase/mozprofile/tests/permissions.py b/testing/mozbase/mozprofile/tests/permissions.py index 5f2798c036f9..0d2f7f7b9e2f 100644 --- a/testing/mozbase/mozprofile/tests/permissions.py +++ b/testing/mozbase/mozprofile/tests/permissions.py @@ -4,15 +4,16 @@ # 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/. +import mozfile import os import shutil +import tempfile +import unittest +from mozprofile.permissions import Permissions try: import sqlite3 except ImportError: from pysqlite2 import dbapi2 as sqlite3 -import tempfile -import unittest -from mozprofile.permissions import Permissions class PermissionsTest(unittest.TestCase): @@ -26,7 +27,7 @@ http://127.0.0.1:8888 privileged def setUp(self): self.profile_dir = tempfile.mkdtemp() - self.locations_file = tempfile.NamedTemporaryFile() + self.locations_file = mozfile.NamedTemporaryFile() self.locations_file.write(self.locations) self.locations_file.flush() @@ -142,7 +143,7 @@ http://127.0.0.1:8888 privileged proxy_check = ("if (isHttp) return 'PROXY mochi.test:8888';", "if (isHttps) return 'PROXY mochi.test:4443';", - "if (isWebSocket) return 'PROXY mochi.test:9988';", + "if (isWebSocket) return 'PROXY mochi.test:4443';", "if (isWebSocketSSL) return 'PROXY mochi.test:4443';") self.assertTrue(all(c in user_prefs[1][1] for c in proxy_check)) diff --git a/testing/mozbase/mozprofile/tests/server_locations.py b/testing/mozbase/mozprofile/tests/server_locations.py index 0c860bd79524..e9ced0deeb1d 100644 --- a/testing/mozbase/mozprofile/tests/server_locations.py +++ b/testing/mozbase/mozprofile/tests/server_locations.py @@ -4,6 +4,7 @@ # 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/. +import mozfile import os import shutil import tempfile @@ -46,7 +47,7 @@ http://example.org:80 privileged self.assertEqual(location.options, options) def create_temp_file(self, contents): - f = tempfile.NamedTemporaryFile() + f = mozfile.NamedTemporaryFile() f.write(contents) f.flush() return f @@ -140,7 +141,7 @@ http://example.org:80 privileged self.assertEqual(len(c.last_locations), 1) self.compare_location(c.last_locations[0], 'http', 'a.b.c', '80', ['privileged']) - + # read a second file, which should generate a callback with both # locations. f = self.create_temp_file(self.locations_no_primary) diff --git a/testing/mozbase/mozprofile/tests/test_preferences.py b/testing/mozbase/mozprofile/tests/test_preferences.py index b41707469b69..cfc8cbe94db4 100755 --- a/testing/mozbase/mozprofile/tests/test_preferences.py +++ b/testing/mozbase/mozprofile/tests/test_preferences.py @@ -4,6 +4,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +import mozfile +import mozhttpd import os import shutil import tempfile @@ -17,6 +19,12 @@ here = os.path.dirname(os.path.abspath(__file__)) class PreferencesTest(unittest.TestCase): """test mozprofile preference handling""" + # preferences from files/prefs_with_comments.js + _prefs_with_comments = {'browser.startup.homepage': 'http://planet.mozilla.org', + 'zoom.minPercent': 30, + 'zoom.maxPercent': 300, + 'webgl.verbose': 'false'} + def run_command(self, *args): """ invokes mozprofile command line via the CLI factory @@ -231,28 +239,49 @@ user_pref("webgl.force-enabled", true); # write them to a temporary location path = None + read_prefs = None try: - with tempfile.NamedTemporaryFile(suffix='.js', delete=False) as f: + with mozfile.NamedTemporaryFile(suffix='.js', delete=False) as f: path = f.name preferences.write(f, _prefs) # read them back and ensure we get what we put in - self.assertEqual(dict(Preferences.read_prefs(path)), _prefs) + read_prefs = dict(Preferences.read_prefs(path)) finally: # cleanup - os.remove(path) + if path and os.path.exists(path): + os.remove(path) + + self.assertEqual(read_prefs, _prefs) def test_read_prefs_with_comments(self): """test reading preferences from a prefs.js file that contains comments""" - _prefs = {'browser.startup.homepage': 'http://planet.mozilla.org', - 'zoom.minPercent': 30, - 'zoom.maxPercent': 300, - 'webgl.verbose': 'false'} path = os.path.join(here, 'files', 'prefs_with_comments.js') - self.assertEqual(dict(Preferences.read_prefs(path)), _prefs) + self.assertEqual(dict(Preferences.read_prefs(path)), self._prefs_with_comments) + def test_read_prefs_ttw(self): + """test reading preferences through the web via mozhttpd""" + + # create a MozHttpd instance + docroot = os.path.join(here, 'files') + host = '127.0.0.1' + port = 8888 + httpd = mozhttpd.MozHttpd(host=host, port=port, docroot=docroot) + + # create a preferences instance + prefs = Preferences() + + try: + # start server + httpd.start(block=False) + + # read preferences through the web + read = prefs.read_prefs('http://%s:%d/prefs_with_comments.js' % (host, port)) + self.assertEqual(dict(read), self._prefs_with_comments) + finally: + httpd.stop() if __name__ == '__main__': unittest.main() diff --git a/testing/mozbase/mozprofile/tests/test_webapps.py b/testing/mozbase/mozprofile/tests/test_webapps.py old mode 100644 new mode 100755 diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk index de3c4d3eb460..e6ad76ab5391 100644 --- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -442,7 +442,7 @@ stage-b2g: make-stage-dir $(NSINSTALL) $(topsrcdir)/b2g/test/b2g-unittest-requirements.txt $(PKG_STAGE)/b2g stage-config: make-stage-dir - $(MAKE) -C $(DEPTH)/testing/config stage-package + $(NSINSTALL) $(topsrcdir)/testing/config/mozharness_config.py $(PKG_STAGE)/config stage-mochitest: make-stage-dir $(MAKE) -C $(DEPTH)/testing/mochitest stage-package diff --git a/testing/xpcshell/moz-spdy/moz-spdy.js b/testing/xpcshell/moz-spdy/moz-spdy.js index d87e0a4ee222..98585b31ab8f 100644 --- a/testing/xpcshell/moz-spdy/moz-spdy.js +++ b/testing/xpcshell/moz-spdy/moz-spdy.js @@ -103,10 +103,33 @@ function handleRequest(req, res) { m.checkReady(); return; } else if (u.pathname == "/header") { + m = new Multiplex(); var val = req.headers["x-test-header"]; if (val) { res.setHeader("X-Received-Test-Header", val); } + } else if (u.pathname == "/push") { + res.push('/push.js', + { 'content-type': 'application/javascript', + 'pushed' : 'yes', + 'content-length' : 11, + 'X-Connection-Spdy': 'yes'}, + function(err, stream) { + if (err) return; + stream.end('// comments'); + }); + content = ' + + + + +

Make sure that nsIConsoleMessages are logged. See bug 859756.

+ + + + diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 341afc106dcc..58f0c41ae224 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -233,7 +233,6 @@ if CONFIG['ENABLE_MARIONETTE']: if CONFIG['ENABLE_TESTS']: add_tier_dir('platform', [ - 'testing/config', 'testing/mochitest', 'testing/xpcshell', 'testing/tools/screenshot', diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 90641ed48c63..3ba46ce54dfe 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -3885,7 +3885,8 @@ XREMain::XRE_mainRun() int XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData) { - GeckoProfilerInitRAII profilerGuard; + char aLocal; + GeckoProfilerInitRAII profilerGuard(&aLocal); PROFILER_LABEL("Startup", "XRE_Main"); nsresult rv = NS_OK; @@ -4080,7 +4081,8 @@ public: int XRE_mainMetro(int argc, char* argv[], const nsXREAppData* aAppData) { - GeckoProfilerInitRAII profilerGuard; + char aLocal; + GeckoProfilerInitRAII profilerGuard(&aLocal); PROFILER_LABEL("Startup", "XRE_Main"); nsresult rv = NS_OK; diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index 824c8c45550b..3f72df30e32b 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -282,7 +282,8 @@ XRE_InitChildProcess(int aArgc, NS_ENSURE_ARG_MIN(aArgc, 2); NS_ENSURE_ARG_POINTER(aArgv); NS_ENSURE_ARG_POINTER(aArgv[0]); - profiler_init(); + char aLocal; + profiler_init(&aLocal); PROFILER_LABEL("Startup", "XRE_InitChildProcess"); sChildProcessType = aProcess; diff --git a/tools/profiler/BreakpadSampler.cpp b/tools/profiler/BreakpadSampler.cpp index 25a9952fd022..54b2b4335bd2 100644 --- a/tools/profiler/BreakpadSampler.cpp +++ b/tools/profiler/BreakpadSampler.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #if defined(ANDROID) diff --git a/tools/profiler/GeckoProfiler.h b/tools/profiler/GeckoProfiler.h index 07a922e39649..a77cc1d3dfd6 100644 --- a/tools/profiler/GeckoProfiler.h +++ b/tools/profiler/GeckoProfiler.h @@ -79,7 +79,7 @@ // Initilize the profiler TLS, signal handlers on linux. If MOZ_PROFILER_STARTUP // is set the profiler will be started. This call must happen before any other // sampler calls. Particularly sampler_label/sampler_marker. -static inline void profiler_init() {}; +static inline void profiler_init(void* stackTop) {}; // Clean up the profiler module, stopping it if required. This function may // also save a shutdown profile if requested. No profiler calls should happen @@ -135,7 +135,7 @@ static inline void profiler_lock() {} // Re-enable the profiler and notify 'profiler-unlocked'. static inline void profiler_unlock() {} -static inline void profiler_register_thread(const char* name) {} +static inline void profiler_register_thread(const char* name, void* stackTop) {} static inline void profiler_unregister_thread() {} // Call by the JSRuntime's operation callback. This is used to enable @@ -152,8 +152,8 @@ static inline double profiler_time() { return 0; } class GeckoProfilerInitRAII { public: - GeckoProfilerInitRAII() { - profiler_init(); + GeckoProfilerInitRAII(void* stackTop) { + profiler_init(stackTop); } ~GeckoProfilerInitRAII() { profiler_shutdown(); diff --git a/tools/profiler/GeckoProfilerFunc.h b/tools/profiler/GeckoProfilerFunc.h index 61d051be389e..97f401eff260 100644 --- a/tools/profiler/GeckoProfilerFunc.h +++ b/tools/profiler/GeckoProfilerFunc.h @@ -42,7 +42,7 @@ JSObject *mozilla_sampler_get_profile_data(JSContext *aCx); const char** mozilla_sampler_get_features(); -void mozilla_sampler_init(); +void mozilla_sampler_init(void* stackTop); void mozilla_sampler_shutdown(); @@ -58,7 +58,7 @@ void mozilla_sampler_lock(); void mozilla_sampler_unlock(); // Register/unregister threads with the profiler -bool mozilla_sampler_register_thread(const char* name); +bool mozilla_sampler_register_thread(const char* name, void* stackTop); void mozilla_sampler_unregister_thread(); double mozilla_sampler_time(); diff --git a/tools/profiler/GeckoProfilerImpl.h b/tools/profiler/GeckoProfilerImpl.h index 6a38507a439f..89779a209384 100644 --- a/tools/profiler/GeckoProfilerImpl.h +++ b/tools/profiler/GeckoProfilerImpl.h @@ -52,9 +52,9 @@ extern bool stack_key_initialized; #endif static inline -void profiler_init() +void profiler_init(void* stackTop) { - mozilla_sampler_init(); + mozilla_sampler_init(stackTop); } static inline @@ -141,9 +141,9 @@ void profiler_unlock() } static inline -void profiler_register_thread(const char* name) +void profiler_register_thread(const char* name, void* stackTop) { - mozilla_sampler_register_thread(name); + mozilla_sampler_register_thread(name, stackTop); } static inline diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp index cc7a2e7cab81..ed63c5698bb9 100644 --- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include +#include #include "GeckoProfilerImpl.h" #include "platform.h" #include "nsThreadUtils.h" diff --git a/tools/profiler/SaveProfileTask.h b/tools/profiler/SaveProfileTask.h index 9b0dd60f19ea..8ea01cc03aa5 100644 --- a/tools/profiler/SaveProfileTask.h +++ b/tools/profiler/SaveProfileTask.h @@ -16,7 +16,7 @@ #include "nsIJSRuntimeService.h" #include "nsIProfileSaveEvent.h" -#include +#include #include #ifdef XP_WIN diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 76507731d84f..b491ec3280d6 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -562,7 +562,7 @@ static void print_callback(const ProfileEntry& entry, const char* tagStringData) void mozilla_sampler_print_location1() { if (!stack_key_initialized) - profiler_init(); + profiler_init(NULL); PseudoStack *stack = tlsPseudoStack.get(); if (!stack) { diff --git a/tools/profiler/UnwinderThread2.cpp b/tools/profiler/UnwinderThread2.cpp index 1095b3c6d44b..ceda4600faa0 100644 --- a/tools/profiler/UnwinderThread2.cpp +++ b/tools/profiler/UnwinderThread2.cpp @@ -26,7 +26,7 @@ #include "PlatformMacros.h" #include "platform.h" -#include +#include #include "ProfileEntry.h" #include "UnwinderThread2.h" @@ -83,6 +83,10 @@ void uwt__register_thread_for_profiling ( void* stackTop ) { } +void uwt__unregister_thread_for_profiling() +{ +} + // RUNS IN SIGHANDLER CONTEXT UnwinderThreadBuffer* uwt__acquire_empty_buffer() { @@ -122,6 +126,9 @@ static int unwind_thr_exit_now = 0; // RACED ON // registered thread. static void thread_register_for_profiling ( void* stackTop ); +// Unregister a thread. +static void thread_unregister_for_profiling(); + // Frees some memory when the unwinder thread is shut down. static void do_breakpad_unwind_Buffer_free_singletons(); @@ -176,6 +183,11 @@ void uwt__register_thread_for_profiling(void* stackTop) thread_register_for_profiling(stackTop); } +void uwt__unregister_thread_for_profiling() +{ + thread_unregister_for_profiling(); +} + // RUNS IN SIGHANDLER CONTEXT UnwinderThreadBuffer* uwt__acquire_empty_buffer() { @@ -348,21 +360,29 @@ typedef /*SL*/ static uint64_t g_seqNo = 0; /*SL*/ static SpinLock g_spinLock = { 0 }; -/* Globals -- the thread array */ -#define N_SAMPLING_THREADS 10 -/*SL*/ static StackLimit g_stackLimits[N_SAMPLING_THREADS]; -/*SL*/ static int g_stackLimitsUsed = 0; +/* Globals -- the thread array. The array is dynamically expanded on + demand. The spinlock must be held when accessing g_stackLimits, + g_stackLimits[some index], g_stackLimitsUsed and g_stackLimitsSize. + However, the spinlock must not be held when calling malloc to + allocate or expand the array, as that would risk deadlock against a + sampling thread that holds the malloc lock and is trying to acquire + the spinlock. */ +/*SL*/ static StackLimit* g_stackLimits = NULL; +/*SL*/ static size_t g_stackLimitsUsed = 0; +/*SL*/ static size_t g_stackLimitsSize = 0; /* Stats -- atomically incremented, no lock needed */ static uintptr_t g_stats_totalSamples = 0; // total # sample attempts static uintptr_t g_stats_noBuffAvail = 0; // # failed due to no buffer avail +static uintptr_t g_stats_thrUnregd = 0; // # failed due to unregistered thr /* We must be VERY CAREFUL what we do with the spinlock held. The only thing it is safe to do with it held is modify (viz, read or write) g_buffers, g_buffers[], g_seqNo, g_buffers[]->state, - g_stackLimits[] and g_stackLimitsUsed. No arbitrary computations, - no syscalls, no printfs, no file IO, and absolutely no dynamic - memory allocation (else we WILL eventually deadlock). + g_stackLimits, g_stackLimits[], g_stackLimitsUsed and + g_stackLimitsSize. No arbitrary computations, no syscalls, no + printfs, no file IO, and absolutely no dynamic memory allocation + (else we WILL eventually deadlock). This applies both to the signal handler and to the unwinder thread. */ @@ -476,44 +496,175 @@ static void atomic_INC(uintptr_t* loc) } } -/* Register a thread for profiling. It must not be allowed to receive - signals before this is done, else the signal handler will - MOZ_ASSERT. */ +// Registers a thread for profiling. Detects and ignores duplicate +// registration. static void thread_register_for_profiling(void* stackTop) { - int i; - /* Minimal sanity check on stackTop */ - MOZ_ASSERT( (void*)&i < stackTop ); + pthread_t me = pthread_self(); spinLock_acquire(&g_spinLock); - pthread_t me = pthread_self(); - for (i = 0; i < g_stackLimitsUsed; i++) { - /* check for duplicate registration */ - MOZ_ASSERT(g_stackLimits[i].thrId != me); + // tmp copy of g_stackLimitsUsed, to avoid racing in message printing + int n_used; + + // Ignore spurious calls which aren't really registering anything. + if (stackTop == NULL) { + n_used = g_stackLimitsUsed; + spinLock_release(&g_spinLock); + LOGF("BPUnw: [%d total] thread_register_for_profiling" + "(me=%p, stacktop=NULL) (IGNORED)", n_used, (void*)me); + return; } - if (!(g_stackLimitsUsed < N_SAMPLING_THREADS)) - MOZ_CRASH(); // Don't continue -- we'll get memory corruption. + + /* Minimal sanity check on stackTop */ + MOZ_ASSERT((void*)&n_used/*any auto var will do*/ < stackTop); + + bool is_dup = false; + for (size_t i = 0; i < g_stackLimitsUsed; i++) { + if (g_stackLimits[i].thrId == me) { + is_dup = true; + break; + } + } + + if (is_dup) { + /* It's a duplicate registration. Ignore it: drop the lock and + return. */ + n_used = g_stackLimitsUsed; + spinLock_release(&g_spinLock); + + LOGF("BPUnw: [%d total] thread_register_for_profiling" + "(me=%p, stacktop=%p) (DUPLICATE)", n_used, (void*)me, stackTop); + return; + } + + /* Make sure the g_stackLimits array is large enough to accommodate + this new entry. This is tricky. If it isn't large enough, we + can malloc a larger version, but we have to do that without + holding the spinlock, else we risk deadlock. The deadlock + scenario is: + + Some other thread that is being sampled + This thread + + call malloc call this function + acquire malloc lock acquire the spinlock + (sampling signal) discover thread array not big enough, + call uwt__acquire_empty_buffer call malloc to make it larger + acquire the spinlock acquire malloc lock + + This gives an inconsistent lock acquisition order on the malloc + lock and spinlock, hence risk of deadlock. + + Allocating more space for the array without holding the spinlock + implies tolerating races against other thread(s) who are also + trying to expand the array. How can we detect if we have been + out-raced? Every successful expansion of g_stackLimits[] results + in an increase in g_stackLimitsSize. Hence we can detect if we + got out-raced by remembering g_stackLimitsSize before we dropped + the spinlock and checking if it has changed after the spinlock is + reacquired. */ + + MOZ_ASSERT(g_stackLimitsUsed <= g_stackLimitsSize); + + if (g_stackLimitsUsed == g_stackLimitsSize) { + /* g_stackLimits[] is full; resize it. */ + + size_t old_size = g_stackLimitsSize; + size_t new_size = old_size == 0 ? 4 : (2 * old_size); + + spinLock_release(&g_spinLock); + StackLimit* new_arr = (StackLimit*)malloc(new_size * sizeof(StackLimit)); + if (!new_arr) + return; + + spinLock_acquire(&g_spinLock); + + if (old_size != g_stackLimitsSize) { + /* We've been outraced. Instead of trying to deal in-line with + this extremely rare case, just start all over again by + tail-calling this routine. */ + spinLock_release(&g_spinLock); + free(new_arr); + thread_register_for_profiling(stackTop); + return; + } + + memcpy(new_arr, g_stackLimits, old_size * sizeof(StackLimit)); + if (g_stackLimits) + free(g_stackLimits); + + g_stackLimits = new_arr; + + MOZ_ASSERT(g_stackLimitsSize < new_size); + g_stackLimitsSize = new_size; + } + + MOZ_ASSERT(g_stackLimitsUsed < g_stackLimitsSize); + + /* Finally, we have a safe place to put the new entry. */ + + // Round |stackTop| up to the end of the containing page. We may + // as well do this -- there's no danger of a fault, and we might + // get a few more base-of-the-stack frames as a result. This + // assumes that no target has a page size smaller than 4096. + uintptr_t stackTopR = (uintptr_t)stackTop; + stackTopR = (stackTopR & ~(uintptr_t)4095) + (uintptr_t)4095; + g_stackLimits[g_stackLimitsUsed].thrId = me; - g_stackLimits[g_stackLimitsUsed].stackTop = stackTop; + g_stackLimits[g_stackLimitsUsed].stackTop = (void*)stackTopR; g_stackLimits[g_stackLimitsUsed].nSamples = 0; g_stackLimitsUsed++; + n_used = g_stackLimitsUsed; spinLock_release(&g_spinLock); - LOGF("BPUnw: thread_register_for_profiling(stacktop %p, me %p)", - stackTop, (void*)me); + + LOGF("BPUnw: [%d total] thread_register_for_profiling" + "(me=%p, stacktop=%p)", n_used, (void*)me, stackTop); +} + +// Deregisters a thread from profiling. Detects and ignores attempts +// to deregister a not-registered thread. +static void thread_unregister_for_profiling() +{ + spinLock_acquire(&g_spinLock); + + // tmp copy of g_stackLimitsUsed, to avoid racing in message printing + size_t n_used; + + size_t i; + bool found = false; + pthread_t me = pthread_self(); + for (i = 0; i < g_stackLimitsUsed; i++) { + if (g_stackLimits[i].thrId == me) + break; + } + if (i < g_stackLimitsUsed) { + // found this entry. Slide the remaining ones down one place. + for (; i+1 < g_stackLimitsUsed; i++) { + g_stackLimits[i] = g_stackLimits[i+1]; + } + g_stackLimitsUsed--; + found = true; + } + + n_used = g_stackLimitsUsed; + + spinLock_release(&g_spinLock); + LOGF("BPUnw: [%d total] thread_unregister_for_profiling(me=%p) %s", + (int)n_used, (void*)me, found ? "" : " (NOT REGISTERED) "); } __attribute__((unused)) static void show_registered_threads() { - int i; + size_t i; spinLock_acquire(&g_spinLock); for (i = 0; i < g_stackLimitsUsed; i++) { LOGF("[%d] pthread_t=%p nSamples=%lld", - i, (void*)g_stackLimits[i].thrId, - (unsigned long long int)g_stackLimits[i].nSamples); + (int)i, (void*)g_stackLimits[i].thrId, + (unsigned long long int)g_stackLimits[i].nSamples); } spinLock_release(&g_spinLock); } @@ -529,7 +680,7 @@ static UnwinderThreadBuffer* acquire_empty_buffer() fillseqno++; and remember it rel lock */ - int i; + size_t i; atomic_INC( &g_stats_totalSamples ); @@ -549,11 +700,20 @@ static UnwinderThreadBuffer* acquire_empty_buffer() is safe to call in a signal handler, which strikes me as highly likely. */ pthread_t me = pthread_self(); - MOZ_ASSERT(g_stackLimitsUsed >= 0 && g_stackLimitsUsed <= N_SAMPLING_THREADS); + MOZ_ASSERT(g_stackLimitsUsed <= g_stackLimitsSize); for (i = 0; i < g_stackLimitsUsed; i++) { if (g_stackLimits[i].thrId == me) break; } + + /* If the thread isn't registered for profiling, just ignore the call + and return NULL. */ + if (i == g_stackLimitsUsed) { + spinLock_release(&g_spinLock); + atomic_INC( &g_stats_thrUnregd ); + return NULL; + } + /* "this thread is registered for profiling" */ MOZ_ASSERT(i < g_stackLimitsUsed); @@ -574,7 +734,7 @@ static UnwinderThreadBuffer* acquire_empty_buffer() if (g_buffers[i]->state == S_EMPTY) break; } - MOZ_ASSERT(i >= 0 && i <= N_UNW_THR_BUFFERS); + MOZ_ASSERT(i <= N_UNW_THR_BUFFERS); if (i == N_UNW_THR_BUFFERS) { /* Again, no free buffers .. give up. */ @@ -1784,9 +1944,11 @@ void do_breakpad_unwind_Buffer(/*OUT*/PCandSP** pairs, if (LOGLEVEL >= 2) { if (0 == (g_stats_totalSamples % 1000)) - LOGF("BPUnw: %llu total samples, %llu failed due to buffer unavail", + LOGF("BPUnw: %llu total samples, %llu failed (buffer unavail), " + "%llu failed (thread unreg'd), ", (unsigned long long int)g_stats_totalSamples, - (unsigned long long int)g_stats_noBuffAvail); + (unsigned long long int)g_stats_noBuffAvail, + (unsigned long long int)g_stats_thrUnregd); } delete stack; diff --git a/tools/profiler/UnwinderThread2.h b/tools/profiler/UnwinderThread2.h index 28bc9275781e..96387df8e5fb 100644 --- a/tools/profiler/UnwinderThread2.h +++ b/tools/profiler/UnwinderThread2.h @@ -37,18 +37,26 @@ void uwt__stop(); // and can safely release any resources. void uwt__deinit(); -// Registers a sampler thread for profiling. Threads must be registered -// before they are allowed to call utb__acquire_empty_buffer or -// utb__release_full_buffer. +// Registers a sampler thread for profiling. Threads must be +// registered before calls to call utb__acquire_empty_buffer or +// utb__release_full_buffer have any effect. If stackTop is +// NULL, the call is ignored. void uwt__register_thread_for_profiling(void* stackTop); -// RUNS IN SIGHANDLER CONTEXT +// Deregister a sampler thread for profiling. +void uwt__unregister_thread_for_profiling(); + +// RUNS IN SIGHANDLER CONTEXT // Called in the sampled thread (signal) context. Get an empty buffer // into which ProfileEntries can be put. It may return NULL if no // empty buffers can be found, which will be the case if the unwinder // thread(s) have fallen behind for some reason. In this case the -// sampled thread must simply give up and return from the signal handler -// immediately, else it risks deadlock. +// sampled thread must simply give up and return from the signal +// handler immediately, else it risks deadlock. +// +// If the calling thread has not previously registered itself for +// profiling via uwt__register_thread_for_profiling, this routine +// returns NULL. UnwinderThreadBuffer* uwt__acquire_empty_buffer(); // RUNS IN SIGHANDLER CONTEXT diff --git a/tools/profiler/platform-linux.cc b/tools/profiler/platform-linux.cc index ae8e02417970..8676e573dac8 100644 --- a/tools/profiler/platform-linux.cc +++ b/tools/profiler/platform-linux.cc @@ -65,6 +65,7 @@ #include "ProfileEntry.h" #include "nsThreadUtils.h" #include "TableTicker.h" +#include "UnwinderThread2.h" #include #include @@ -358,7 +359,9 @@ void Sampler::Stop() { } } -bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread) +bool Sampler::RegisterCurrentThread(const char* aName, + PseudoStack* aPseudoStack, + bool aIsMainThread, void* stackTop) { if (!Sampler::sRegisteredThreadsMutex) return false; @@ -385,6 +388,8 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack } sRegisteredThreads->push_back(info); + + uwt__register_thread_for_profiling(stackTop); return true; } @@ -405,6 +410,8 @@ void Sampler::UnregisterCurrentThread() break; } } + + uwt__unregister_thread_for_profiling(); } #ifdef ANDROID diff --git a/tools/profiler/platform-macos.cc b/tools/profiler/platform-macos.cc index 2dac97f9cc75..8630f7c9b3e3 100644 --- a/tools/profiler/platform-macos.cc +++ b/tools/profiler/platform-macos.cc @@ -341,7 +341,9 @@ pid_t gettid() return (pid_t) syscall(SYS_thread_selfid); } -bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread) +bool Sampler::RegisterCurrentThread(const char* aName, + PseudoStack* aPseudoStack, + bool aIsMainThread, void* stackTop) { if (!Sampler::sRegisteredThreadsMutex) return false; @@ -368,6 +370,8 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack } sRegisteredThreads->push_back(info); + + uwt__register_thread_for_profiling(stackTop); return true; } diff --git a/tools/profiler/platform-win32.cc b/tools/profiler/platform-win32.cc index ad4eef407802..00e5ff15c22f 100644 --- a/tools/profiler/platform-win32.cc +++ b/tools/profiler/platform-win32.cc @@ -32,6 +32,7 @@ #include "platform.h" #include "TableTicker.h" #include "ProfileEntry.h" +#include "UnwinderThread2.h" class PlatformData : public Malloced { public: @@ -261,7 +262,9 @@ void OS::Sleep(int milliseconds) { ::Sleep(milliseconds); } -bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread) +bool Sampler::RegisterCurrentThread(const char* aName, + PseudoStack* aPseudoStack, + bool aIsMainThread, void* stackTop) { if (!Sampler::sRegisteredThreadsMutex) return false; @@ -288,6 +291,8 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack } sRegisteredThreads->push_back(info); + + uwt__register_thread_for_profiling(stackTop); return true; } diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index b20f10ff3203..9020abf90c35 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include +#include #include #include #include @@ -252,7 +252,7 @@ void read_profiler_env_vars() //////////////////////////////////////////////////////////////////////// // BEGIN externally visible functions -void mozilla_sampler_init() +void mozilla_sampler_init(void* stackTop) { sInitCount++; @@ -271,7 +271,7 @@ void mozilla_sampler_init() PseudoStack *stack = new PseudoStack(); tlsPseudoStack.set(stack); - Sampler::RegisterCurrentThread("Gecko", stack, true); + Sampler::RegisterCurrentThread("Gecko", stack, true, stackTop); // Read mode settings from MOZ_PROFILER_MODE and interval // settings from MOZ_PROFILER_INTERVAL and stack-scan threshhold @@ -402,7 +402,7 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval, const char** aFeatures, uint32_t aFeatureCount) { if (!stack_key_initialized) - profiler_init(); + profiler_init(NULL); /* If the sampling interval was set using env vars, use that in preference to anything else. */ @@ -423,9 +423,6 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval, aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY, aFeatures, aFeatureCount); if (t->HasUnwinderThread()) { - int aLocal; - uwt__register_thread_for_profiling( &aLocal ); - // Create the unwinder thread. ATM there is only one. uwt__init(); } @@ -467,7 +464,7 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval, void mozilla_sampler_stop() { if (!stack_key_initialized) - profiler_init(); + profiler_init(NULL); TableTicker *t = tlsTicker.get(); if (!t) { @@ -559,12 +556,12 @@ void mozilla_sampler_unlock() os->NotifyObservers(nullptr, "profiler-unlocked", nullptr); } -bool mozilla_sampler_register_thread(const char* aName) +bool mozilla_sampler_register_thread(const char* aName, void* stackTop) { PseudoStack* stack = new PseudoStack(); tlsPseudoStack.set(stack); - return Sampler::RegisterCurrentThread(aName, stack, false); + return Sampler::RegisterCurrentThread(aName, stack, false, stackTop); } void mozilla_sampler_unregister_thread() diff --git a/tools/profiler/platform.h b/tools/profiler/platform.h index 68d2a3a22236..9052c232f047 100644 --- a/tools/profiler/platform.h +++ b/tools/profiler/platform.h @@ -335,7 +335,9 @@ class Sampler { return *sRegisteredThreads; } - static bool RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread); + static bool RegisterCurrentThread(const char* aName, + PseudoStack* aPseudoStack, + bool aIsMainThread, void* stackTop); static void UnregisterCurrentThread(); static void Startup(); diff --git a/view/src/nsView.cpp b/view/src/nsView.cpp index fc66617d216e..f482f28f2c10 100644 --- a/view/src/nsView.cpp +++ b/view/src/nsView.cpp @@ -94,6 +94,24 @@ nsView::~nsView() delete mDirtyRegion; } +class DestroyWidgetRunnable : public nsRunnable { +public: + NS_DECL_NSIRUNNABLE + + explicit DestroyWidgetRunnable(nsIWidget* aWidget) : mWidget(aWidget) {} + +private: + nsCOMPtr mWidget; +}; + +NS_IMETHODIMP DestroyWidgetRunnable::Run() +{ + mWidget->Destroy(); + mWidget = nullptr; + return NS_OK; +} + + void nsView::DestroyWidget() { if (mWindow) @@ -107,7 +125,11 @@ void nsView::DestroyWidget() } else { mWindow->SetWidgetListener(nullptr); - mWindow->Destroy(); + + nsCOMPtr widgetDestroyer = + new DestroyWidgetRunnable(mWindow); + + NS_DispatchToMainThread(widgetDestroyer); } NS_RELEASE(mWindow); diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index c5f611bd3971..8afd5b6890ef 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -188,6 +188,8 @@ public: eIntID_MenusCanOverlapOSBar, // should overlay scrollbars be used? eIntID_UseOverlayScrollbars, + // allow H and V overlay scrollbars to overlap? + eIntID_AllowOverlayScrollbarsOverlap, // show/hide scrollbars based on activity eIntID_ShowHideScrollbars, // skip navigating to disabled menu item? diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 3370e4bb9858..20308a7bbdb1 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -2038,6 +2038,22 @@ AndroidBridge::LockWindow(void *window, unsigned char **bits, int *width, int *h return true; } +jobject +AndroidBridge::GetContext() { + JNIEnv *env = GetJNIForThread(); + if (!env) + return 0; + + jobject context = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetContext); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return 0; + } + + return context; +} + jobject AndroidBridge::GetGlobalContextRef() { JNIEnv *env = GetJNIForThread(); @@ -2046,10 +2062,7 @@ AndroidBridge::GetGlobalContextRef() { AutoLocalJNIFrame jniFrame(env, 0); - jobject context = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetContext); - if (jniFrame.CheckForException()) { - return 0; - } + jobject context = GetContext(); jobject globalRef = env->NewGlobalRef(context); MOZ_ASSERT(globalRef); diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index 66326f72d699..07697857418c 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -310,6 +310,9 @@ public: // DeleteGlobalRef() when the context is no longer needed. jobject GetGlobalContextRef(void); + // Returns a local reference. Caller must manage this reference + jobject GetContext(void); + void UnlockBitmap(jobject bitmap); bool UnlockProfile(); diff --git a/widget/cocoa/nsLookAndFeel.h b/widget/cocoa/nsLookAndFeel.h index 563bf779d389..4ff9b20f265f 100644 --- a/widget/cocoa/nsLookAndFeel.h +++ b/widget/cocoa/nsLookAndFeel.h @@ -31,6 +31,7 @@ protected: static const int kThemeScrollBarArrowsUpperLeft = 3; static bool UseOverlayScrollbars(); + static bool AllowOverlayScrollbarsOverlap(); }; #endif // nsLookAndFeel_h_ diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm index a98a5425a191..870a5f968b21 100644 --- a/widget/cocoa/nsLookAndFeel.mm +++ b/widget/cocoa/nsLookAndFeel.mm @@ -8,6 +8,7 @@ #include "nsIServiceManager.h" #include "nsNativeThemeColors.h" #include "nsStyleConsts.h" +#include "nsCocoaFeatures.h" #include "gfxFont.h" #import @@ -352,7 +353,10 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) aResult = eScrollThumbStyle_Proportional; break; case eIntID_UseOverlayScrollbars: - aResult = UseOverlayScrollbars(); + aResult = UseOverlayScrollbars() ? 1 : 0; + break; + case eIntID_AllowOverlayScrollbarsOverlap: + aResult = AllowOverlayScrollbarsOverlap() ? 1 : 0; break; case eIntID_TreeOpenDelay: aResult = 1000; @@ -468,8 +472,13 @@ nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult) bool nsLookAndFeel::UseOverlayScrollbars() { - return [NSScroller respondsToSelector:@selector(preferredScrollerStyle)] && - [NSScroller preferredScrollerStyle] == mozNSScrollerStyleOverlay; + return ([NSScroller respondsToSelector:@selector(preferredScrollerStyle)] && + [NSScroller preferredScrollerStyle] == mozNSScrollerStyleOverlay); +} + +bool nsLookAndFeel::AllowOverlayScrollbarsOverlap() +{ + return (UseOverlayScrollbars() && nsCocoaFeatures::OnMountainLionOrLater()); } // copied from gfxQuartzFontCache.mm, maybe should go in a Cocoa utils diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm index 4a2760a3c587..6b6098e68b4b 100644 --- a/widget/cocoa/nsNativeThemeCocoa.mm +++ b/widget/cocoa/nsNativeThemeCocoa.mm @@ -2679,7 +2679,7 @@ nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFram } static const int32_t kRegularScrollbarThumbMinSize = 26; -static const int32_t kSmallScrollbarThumbMinSize = 19; +static const int32_t kSmallScrollbarThumbMinSize = 26; NS_IMETHODIMP nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext, diff --git a/widget/tests/test_bug485118.xul b/widget/tests/test_bug485118.xul index 8f82ae7fd4ec..f17b4e1a20a5 100644 --- a/widget/tests/test_bug485118.xul +++ b/widget/tests/test_bug485118.xul @@ -58,7 +58,8 @@ function runTest() { var elem = document.getElementById(orient + size); var thumbRect = document.getAnonymousElementByAttribute(elem, 'sbattr', 'scrollbar-thumb').getBoundingClientRect(); var sizeToCheck = orient == "horizontal" ? "width" : "height"; - var expectedSize = size == "Small" ? 19 : 26; + // var expectedSize = size == "Small" ? 19 : 26; + var expectedSize = 26; is(thumbRect[sizeToCheck], expectedSize, size + " scrollbar has wrong minimum " + sizeToCheck); }); }); diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp index 2c93aa2d15ef..f00ad0168b56 100644 --- a/widget/windows/JumpListBuilder.cpp +++ b/widget/windows/JumpListBuilder.cpp @@ -17,8 +17,6 @@ #include "nsDirectoryServiceUtils.h" #include "nsISimpleEnumerator.h" #include "mozilla/Preferences.h" -#include "imgIContainer.h" -#include "imgITools.h" #include "nsStringStream.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp index cbbf4727913d..546f37a30b12 100644 --- a/widget/windows/KeyboardLayout.cpp +++ b/widget/windows/KeyboardLayout.cpp @@ -6,7 +6,6 @@ #include "mozilla/Util.h" #include "KeyboardLayout.h" -#include "nsWindow.h" #include "nsIMM32Handler.h" #include "nsMemory.h" @@ -17,8 +16,14 @@ #include "nsUnicharUtils.h" #include "WidgetUtils.h" #include "WinUtils.h" +#include "nsWindowDbg.h" +#include "nsServiceManagerUtils.h" +#include "nsPrintfCString.h" #include "nsIDOMKeyEvent.h" +#include "nsIIdleServiceInternal.h" + +#include "npapi.h" #include #include @@ -381,31 +386,49 @@ VirtualKey::FillKbdState(PBYTE aKbdState, * mozilla::widget::NativeKey *****************************************************************************/ -NativeKey::NativeKey(const KeyboardLayout& aKeyboardLayout, - nsWindow* aWindow, - const MSG& aKeyOrCharMessage) : - mDOMKeyCode(0), mMessage(aKeyOrCharMessage.message), - mVirtualKeyCode(0), mOriginalVirtualKeyCode(0) +NativeKey::NativeKey(nsWindowBase* aWidget, + const MSG& aKeyOrCharMessage, + const ModifierKeyState& aModKeyState, + const FakeCharMsg* aFakeCharMsg) : + mWidget(aWidget), mMsg(aKeyOrCharMessage), mDOMKeyCode(0), + mModKeyState(aModKeyState), mVirtualKeyCode(0), mOriginalVirtualKeyCode(0), + mIsFakeCharMsg(false) { - mScanCode = WinUtils::GetScanCode(aKeyOrCharMessage.lParam); - mIsExtended = WinUtils::IsExtendedScanCode(aKeyOrCharMessage.lParam); + MOZ_ASSERT(aWidget); + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + mKeyboardLayout = keyboardLayout->GetLayout(); + mScanCode = WinUtils::GetScanCode(mMsg.lParam); + mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam); + memset(&mCharMsg, 0, sizeof(MSG)); // On WinXP and WinServer2003, we cannot compute the virtual keycode for // extended keys due to the API limitation. bool canComputeVirtualKeyCodeFromScanCode = (!mIsExtended || WinUtils::GetWindowsVersion() >= WinUtils::VISTA_VERSION); - switch (mMessage) { + switch (mMsg.message) { case WM_KEYDOWN: - case WM_KEYUP: case WM_SYSKEYDOWN: + // Store following WM_*CHAR message into mCharMsg. + if (aFakeCharMsg) { + mCharMsg = aFakeCharMsg->GetCharMsg(mMsg.hwnd); + mIsFakeCharMsg = true; + } else { + MSG msg; + if (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD) && + (msg.message == WM_CHAR || msg.message == WM_SYSCHAR || + msg.message == WM_DEADCHAR)) { + mCharMsg = msg; + } + } + case WM_KEYUP: case WM_SYSKEYUP: { // First, resolve the IME converted virtual keycode to its original // keycode. - if (aKeyOrCharMessage.wParam == VK_PROCESSKEY) { - mOriginalVirtualKeyCode = static_cast( - ::ImmGetVirtualKey(aWindow->GetWindowHandle())); - } else { + if (mMsg.wParam == VK_PROCESSKEY) { mOriginalVirtualKeyCode = - static_cast(aKeyOrCharMessage.wParam); + static_cast(::ImmGetVirtualKey(mMsg.hwnd)); + } else { + mOriginalVirtualKeyCode = static_cast(mMsg.wParam); } // Most keys are not distinguished as left or right keys. @@ -484,9 +507,7 @@ NativeKey::NativeKey(const KeyboardLayout& aKeyboardLayout, "mVirtualKeyCode has been computed already"); // Otherwise, compute the virtual keycode with MapVirtualKeyEx(). - mVirtualKeyCode = static_cast( - ::MapVirtualKeyEx(GetScanCodeWithExtendedFlag(), - MAPVK_VSC_TO_VK_EX, aKeyboardLayout.GetLayout())); + mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx(); // The result might be unexpected value due to the scan code is // wrong. For example, any key messages can be generated by @@ -526,9 +547,8 @@ NativeKey::NativeKey(const KeyboardLayout& aKeyboardLayout, if (!canComputeVirtualKeyCodeFromScanCode) { break; } - mVirtualKeyCode = mOriginalVirtualKeyCode = static_cast( - ::MapVirtualKeyEx(GetScanCodeWithExtendedFlag(), - MAPVK_VSC_TO_VK_EX, aKeyboardLayout.GetLayout())); + mVirtualKeyCode = mOriginalVirtualKeyCode = + ComputeVirtualKeyCodeFromScanCodeEx(); break; default: MOZ_NOT_REACHED("Unsupported message"); @@ -540,9 +560,50 @@ NativeKey::NativeKey(const KeyboardLayout& aKeyboardLayout, } mDOMKeyCode = - aKeyboardLayout.ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode); + keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode); mKeyNameIndex = - aKeyboardLayout.ConvertNativeKeyCodeToKeyNameIndex(mOriginalVirtualKeyCode); + keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mOriginalVirtualKeyCode); + + keyboardLayout->InitNativeKey(*this, mModKeyState); + + mIsDeadKey = + (mCharMsg.message == WM_DEADCHAR || + keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState)); + mIsPrintableKey = KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode); +} + +bool +NativeKey::IsIMEDoingKakuteiUndo() const +{ + // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG: + // --------------------------------------------------------------------------- + // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK + // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0) + // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF) + // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001) + // --------------------------------------------------------------------------- + // This doesn't match usual key message pattern such as: + // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP + // See following bugs for the detail. + // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese) + // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English) + MSG startCompositionMsg, compositionMsg, charMsg; + return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd, + WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, + PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION, + WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR, + PM_NOREMOVE | PM_NOYIELD) && + startCompositionMsg.wParam == 0x0 && + startCompositionMsg.lParam == 0x0 && + compositionMsg.wParam == 0x0 && + compositionMsg.lParam == 0x1BF && + charMsg.wParam == VK_BACK && charMsg.lParam == 0x1 && + startCompositionMsg.time <= compositionMsg.time && + compositionMsg.time <= charMsg.time; } UINT @@ -624,18 +685,719 @@ NativeKey::GetKeyLocation() const } } +uint8_t +NativeKey::ComputeVirtualKeyCodeFromScanCode() const +{ + return static_cast( + ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout)); +} + +uint8_t +NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const +{ + bool VistaOrLater = + (WinUtils::GetWindowsVersion() >= WinUtils::VISTA_VERSION); + // NOTE: WinXP doesn't support mapping scan code to virtual keycode of + // extended keys. + NS_ENSURE_TRUE(!mIsExtended || VistaOrLater, 0); + return static_cast( + ::MapVirtualKeyEx(GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, + mKeyboardLayout)); +} + +PRUnichar +NativeKey::ComputeUnicharFromScanCode() const +{ + return static_cast( + ::MapVirtualKeyEx(ComputeVirtualKeyCodeFromScanCode(), + MAPVK_VK_TO_CHAR, mKeyboardLayout)); +} + +void +NativeKey::InitKeyEvent(nsKeyEvent& aKeyEvent, + const ModifierKeyState& aModKeyState) const +{ + nsIntPoint point(0, 0); + mWidget->InitEvent(aKeyEvent, &point); + + switch (aKeyEvent.message) { + case NS_KEY_DOWN: + aKeyEvent.keyCode = mDOMKeyCode; + break; + case NS_KEY_UP: + aKeyEvent.keyCode = mDOMKeyCode; + // Set defaultPrevented of the key event if the VK_MENU is not a system + // key release, so that the menu bar does not trigger. This helps avoid + // triggering the menu bar for ALT key accelerators used in assistive + // technologies such as Window-Eyes and ZoomText or for switching open + // state of IME. + aKeyEvent.mFlags.mDefaultPrevented = + (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP); + break; + case NS_KEY_PRESS: + break; + default: + MOZ_NOT_REACHED("Invalid event message"); + break; + } + + aKeyEvent.mKeyNameIndex = mKeyNameIndex; + aKeyEvent.location = GetKeyLocation(); + aModKeyState.InitInputEvent(aKeyEvent); +} + +bool +NativeKey::DispatchKeyEvent(nsKeyEvent& aKeyEvent, + const MSG* aMsgSentToPlugin) const +{ + KeyboardLayout::NotifyIdleServiceOfUserActivity(); + + NPEvent pluginEvent; + if (aMsgSentToPlugin && + mWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) { + pluginEvent.event = aMsgSentToPlugin->message; + pluginEvent.wParam = aMsgSentToPlugin->wParam; + pluginEvent.lParam = aMsgSentToPlugin->lParam; + aKeyEvent.pluginEvent = static_cast(&pluginEvent); + } + + return mWidget->DispatchWindowEvent(&aKeyEvent); +} + +bool +NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + bool defaultPrevented = false; + if (mIsFakeCharMsg || + !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { + // Ignore [shift+]alt+space so the OS can handle it. + if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && + mVirtualKeyCode == VK_SPACE) { + return false; + } + + bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext()); + nsKeyEvent keydownEvent(true, NS_KEY_DOWN, mWidget); + InitKeyEvent(keydownEvent, mModKeyState); + if (aEventDispatched) { + *aEventDispatched = true; + } + defaultPrevented = DispatchKeyEvent(keydownEvent, &mMsg); + + // If IMC wasn't associated to the window but is associated it now (i.e., + // focus is moved from a non-editable editor to an editor by keydown + // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character + // inputting if IME is opened. But then, we should redirect the native + // keydown message to IME. + // However, note that if focus has been already moved to another + // application, we shouldn't redirect the message to it because the keydown + // message is processed by us, so, nobody shouldn't process it. + HWND focusedWnd = ::GetFocus(); + if (!defaultPrevented && !mIsFakeCharMsg && focusedWnd && + !mWidget->PluginHasFocus() && !isIMEEnabled && + WinUtils::IsIMEEnabled(mWidget->GetInputContext())) { + RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd); + + INPUT keyinput; + keyinput.type = INPUT_KEYBOARD; + keyinput.ki.wVk = mOriginalVirtualKeyCode; + keyinput.ki.wScan = mScanCode; + keyinput.ki.dwFlags = KEYEVENTF_SCANCODE; + if (mIsExtended) { + keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + keyinput.ki.time = 0; + keyinput.ki.dwExtraInfo = 0; + + RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented); + + ::SendInput(1, &keyinput, sizeof(keyinput)); + + // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN. + // If it's needed, it will be dispatched after next (redirected) + // WM_KEYDOWN. + return true; + } + + if (mWidget->Destroyed()) { + // If this was destroyed by the keydown event handler, we shouldn't + // dispatch keypress event on this window. + return true; + } + } else { + defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented(); + // If this is redirected keydown message, we have dispatched the keydown + // event already. + if (aEventDispatched) { + *aEventDispatched = true; + } + } + + RedirectedKeyDownMessageManager::Forget(); + + // If the key was processed by IME, we shouldn't dispatch keypress event. + if (mOriginalVirtualKeyCode == VK_PROCESSKEY) { + return defaultPrevented; + } + + // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a keypress + // for almost all keys + switch (mDOMKeyCode) { + case NS_VK_SHIFT: + case NS_VK_CONTROL: + case NS_VK_ALT: + case NS_VK_CAPS_LOCK: + case NS_VK_NUM_LOCK: + case NS_VK_SCROLL_LOCK: + case NS_VK_WIN: + return defaultPrevented; + } + + EventFlags extraFlags; + extraFlags.mDefaultPrevented = defaultPrevented; + + if (NeedsToHandleWithoutFollowingCharMessages()) { + return DispatchKeyPressEventsAndDiscardsCharMessages(extraFlags); + } + + if (IsFollowedByCharMessage()) { + return DispatchKeyPressEventForFollowingCharMessage(extraFlags); + } + + if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && + !mModKeyState.IsWin() && mIsPrintableKey) { + // If this is simple KeyDown event but next message is not WM_CHAR, + // this event may not input text, so we should ignore this event. + // See bug 314130. + return mWidget->PluginHasFocus() && defaultPrevented; + } + + if (mIsDeadKey) { + return mWidget->PluginHasFocus() && defaultPrevented; + } + + return DispatchKeyPressEventsWithKeyboardLayout(extraFlags); +} + +bool +NativeKey::HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched, + const EventFlags* aExtraFlags) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN || + mMsg.message == WM_CHAR || mMsg.message == WM_SYSCHAR); + MOZ_ASSERT(aCharMsg.message == WM_CHAR || aCharMsg.message == WM_SYSCHAR); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + // Alt+Space key is handled by OS, we shouldn't touch it. + if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && + mVirtualKeyCode == VK_SPACE) { + return false; + } + + // Bug 818235: Ignore Ctrl+Enter. + if (!mModKeyState.IsAlt() && mModKeyState.IsControl() && + mVirtualKeyCode == VK_RETURN) { + return false; + } + + // XXXmnakao I think that if aNativeKeyDown is null, such lonely WM_CHAR + // should cause composition events because they are not caused + // by actual keyboard operation. + + static const PRUnichar U_SPACE = 0x20; + static const PRUnichar U_EQUAL = 0x3D; + + // First, handle normal text input or non-printable key case here. + if ((!mModKeyState.IsAlt() && !mModKeyState.IsControl()) || + mModKeyState.IsAltGr() || + (mOriginalVirtualKeyCode && + !KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode))) { + nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget); + if (aExtraFlags) { + keypressEvent.mFlags.Union(*aExtraFlags); + } + if (aCharMsg.wParam >= U_SPACE) { + keypressEvent.charCode = static_cast(aCharMsg.wParam); + } else { + keypressEvent.keyCode = mDOMKeyCode; + } + // When AltGr (Alt+Ctrl) is pressed, that causes normal text input. + // At this time, if either alt or ctrl flag is set, nsEditor ignores the + // keypress event. For avoiding this issue, we should remove ctrl and alt + // flags. + ModifierKeyState modKeyState(mModKeyState); + modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + InitKeyEvent(keypressEvent, modKeyState); + if (aEventDispatched) { + *aEventDispatched = true; + } + return DispatchKeyEvent(keypressEvent, &aCharMsg); + } + + // XXX It seems that following code was implemented for shortcut key + // handling. However, it's now handled in WM_KEYDOWN message handler. + // So, this actually runs only when WM_CHAR is sent/posted without + // WM_KEYDOWN. I think that we don't need to keypress event in such + // case especially for shortcut keys. + + PRUnichar uniChar; + // Ctrl+A Ctrl+Z, see Programming Windows 3.1 page 110 for details + if (mModKeyState.IsControl() && aCharMsg.wParam <= 0x1A) { + // Bug 16486: Need to account for shift here. + uniChar = aCharMsg.wParam - 1 + (mModKeyState.IsShift() ? 'A' : 'a'); + } else if (mModKeyState.IsControl() && aCharMsg.wParam <= 0x1F) { + // Bug 50255: <[> and <]> are not being processed. + // also fixes ctrl+\ (x1c), ctrl+^ (x1e) and ctrl+_ (x1f) + // for some reason the keypress handler need to have the uniChar code set + // with the addition of a upper case A not the lower case. + uniChar = aCharMsg.wParam - 1 + 'A'; + } else if (aCharMsg.wParam < U_SPACE || + (aCharMsg.wParam == U_EQUAL && mModKeyState.IsControl())) { + uniChar = 0; + } else { + uniChar = aCharMsg.wParam; + } + + // Bug 50255 and Bug 351310: Keep the characters unshifted for shortcuts and + // accesskeys and make sure that numbers are always passed as such. + if (uniChar && (mModKeyState.IsControl() || mModKeyState.IsAlt())) { + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + PRUnichar unshiftedCharCode = + (mVirtualKeyCode >= '0' && mVirtualKeyCode <= '9') ? + mVirtualKeyCode : mModKeyState.IsShift() ? + ComputeUnicharFromScanCode() : 0; + // Ignore diacritics (top bit set) and key mapping errors (char code 0) + if (static_cast(unshiftedCharCode) > 0) { + uniChar = unshiftedCharCode; + } + } + + // Bug 285161 and Bug 295095: They were caused by the initial fix for + // bug 178110. When pressing (alt|ctrl)+char, the char must be lowercase + // unless shift is pressed too. + if (!mModKeyState.IsShift() && + (mModKeyState.IsAlt() || mModKeyState.IsControl())) { + uniChar = towlower(uniChar); + } + + nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget); + if (aExtraFlags) { + keypressEvent.mFlags.Union(*aExtraFlags); + } + keypressEvent.charCode = uniChar; + if (!keypressEvent.charCode) { + keypressEvent.keyCode = mDOMKeyCode; + } + InitKeyEvent(keypressEvent, mModKeyState); + if (aEventDispatched) { + *aEventDispatched = true; + } + return DispatchKeyEvent(keypressEvent, &aCharMsg); +} + +bool +NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + // Ignore [shift+]alt+space so the OS can handle it. + if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && + mVirtualKeyCode == VK_SPACE) { + return false; + } + + nsKeyEvent keyupEvent(true, NS_KEY_UP, mWidget); + InitKeyEvent(keyupEvent, mModKeyState); + if (aEventDispatched) { + *aEventDispatched = true; + } + return DispatchKeyEvent(keyupEvent, &mMsg); +} + +bool +NativeKey::NeedsToHandleWithoutFollowingCharMessages() const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + + // Enter and backspace are always handled here to avoid for example the + // confusion between ctrl-enter and ctrl-J. + if (mDOMKeyCode == NS_VK_RETURN || mDOMKeyCode == NS_VK_BACK) { + return true; + } + + // If any modifier keys which may cause printable keys becoming non-printable + // are not pressed, we don't need special handling for the key. + if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && + !mModKeyState.IsWin()) { + return false; + } + + // If the key event causes dead key event, we don't need to dispatch keypress + // event. + if (mIsDeadKey) { + return false; + } + + // Even if the key is a printable key, it might cause non-printable character + // input with modifier key(s). + return mIsPrintableKey; +} + +const MSG& +NativeKey::RemoveFollowingCharMessage() const +{ + MOZ_ASSERT(IsFollowedByCharMessage()); + + if (mIsFakeCharMsg) { + return mCharMsg; + } + + MSG msg; + if (!WinUtils::GetMessage(&msg, mMsg.hwnd, + mCharMsg.message, mCharMsg.message)) { + MOZ_NOT_REACHED("We lost the following char message"); + return mCharMsg; + } + + MOZ_ASSERT(mCharMsg.message == msg.message && + mCharMsg.wParam == msg.wParam && + mCharMsg.lParam == msg.lParam); + + return mCharMsg; +} + +void +NativeKey::RemoveMessageAndDispatchPluginEvent(UINT aFirstMsg, + UINT aLastMsg) const +{ + MSG msg; + if (mIsFakeCharMsg) { + if (aFirstMsg > WM_CHAR || aLastMsg < WM_CHAR) { + return; + } + msg = mCharMsg; + } else { + WinUtils::GetMessage(&msg, mMsg.hwnd, aFirstMsg, aLastMsg); + } + mWidget->DispatchPluginEvent(msg); +} + +bool +NativeKey::DispatchKeyPressEventsAndDiscardsCharMessages( + const EventFlags& aExtraFlags) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + + // Remove a possible WM_CHAR or WM_SYSCHAR messages from the message queue. + // They can be more than one because of: + // * Dead-keys not pairing with base character + // * Some keyboard layouts may map up to 4 characters to the single key + bool anyCharMessagesRemoved = false; + + if (mIsFakeCharMsg) { + RemoveMessageAndDispatchPluginEvent(WM_KEYFIRST, WM_KEYLAST); + anyCharMessagesRemoved = true; + } else { + MSG msg; + bool gotMsg = + WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD); + while (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { + RemoveMessageAndDispatchPluginEvent(WM_KEYFIRST, WM_KEYLAST); + anyCharMessagesRemoved = true; + gotMsg = WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD); + } + } + + if (!anyCharMessagesRemoved && + mDOMKeyCode == NS_VK_BACK && IsIMEDoingKakuteiUndo()) { + MOZ_ASSERT(!mIsFakeCharMsg); + RemoveMessageAndDispatchPluginEvent(WM_CHAR, WM_CHAR); + } + + return DispatchKeyPressEventsWithKeyboardLayout(aExtraFlags); +} + +bool +NativeKey::DispatchKeyPressEventsWithKeyboardLayout( + const EventFlags& aExtraFlags) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + MOZ_ASSERT(!mIsDeadKey); + + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + + UniCharsAndModifiers inputtingChars(mCommittedCharsAndModifiers); + UniCharsAndModifiers shiftedChars; + UniCharsAndModifiers unshiftedChars; + uint32_t shiftedLatinChar = 0; + uint32_t unshiftedLatinChar = 0; + + if (!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode)) { + inputtingChars.Clear(); + } + + if (mModKeyState.IsControl() ^ mModKeyState.IsAlt()) { + ModifierKeyState capsLockState( + mModKeyState.GetModifiers() & MODIFIER_CAPSLOCK); + + unshiftedChars = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + capsLockState.Set(MODIFIER_SHIFT); + shiftedChars = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + + // The current keyboard cannot input alphabets or numerics, + // we should append them for Shortcut/Access keys. + // E.g., for Cyrillic keyboard layout. + capsLockState.Unset(MODIFIER_SHIFT); + WidgetUtils::GetLatinCharCodeForKeyCode(mDOMKeyCode, + capsLockState.GetModifiers(), + &unshiftedLatinChar, + &shiftedLatinChar); + + // If the shiftedLatinChar isn't 0, the key code is NS_VK_[A-Z]. + if (shiftedLatinChar) { + // If the produced characters of the key on current keyboard layout + // are same as computed Latin characters, we shouldn't append the + // Latin characters to alternativeCharCode. + if (unshiftedLatinChar == unshiftedChars.mChars[0] && + shiftedLatinChar == shiftedChars.mChars[0]) { + shiftedLatinChar = unshiftedLatinChar = 0; + } + } else if (unshiftedLatinChar) { + // If the shiftedLatinChar is 0, the keyCode doesn't produce + // alphabet character. At that time, the character may be produced + // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT + // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without + // Shift key but with Shift key, it produces '%'. + // If the unshiftedLatinChar is produced by the key on current + // keyboard layout, we shouldn't append it to alternativeCharCode. + if (unshiftedLatinChar == unshiftedChars.mChars[0] || + unshiftedLatinChar == shiftedChars.mChars[0]) { + unshiftedLatinChar = 0; + } + } + + // If the charCode is not ASCII character, we should replace the + // charCode with ASCII character only when Ctrl is pressed. + // But don't replace the charCode when the charCode is not same as + // unmodified characters. In such case, Ctrl is sometimes used for a + // part of character inputting key combination like Shift. + if (mModKeyState.IsControl()) { + uint32_t ch = + mModKeyState.IsShift() ? shiftedLatinChar : unshiftedLatinChar; + if (ch && + (!inputtingChars.mLength || + inputtingChars.UniCharsCaseInsensitiveEqual( + mModKeyState.IsShift() ? shiftedChars : unshiftedChars))) { + inputtingChars.Clear(); + inputtingChars.Append(ch, mModKeyState.GetModifiers()); + } + } + } + + if (inputtingChars.IsEmpty() && + shiftedChars.IsEmpty() && unshiftedChars.IsEmpty()) { + nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget); + keypressEvent.mFlags.Union(aExtraFlags); + keypressEvent.keyCode = mDOMKeyCode; + InitKeyEvent(keypressEvent, mModKeyState); + return (DispatchKeyEvent(keypressEvent) || aExtraFlags.mDefaultPrevented); + } + + uint32_t longestLength = + std::max(inputtingChars.mLength, + std::max(shiftedChars.mLength, unshiftedChars.mLength)); + uint32_t skipUniChars = longestLength - inputtingChars.mLength; + uint32_t skipShiftedChars = longestLength - shiftedChars.mLength; + uint32_t skipUnshiftedChars = longestLength - unshiftedChars.mLength; + UINT keyCode = !inputtingChars.mLength ? mDOMKeyCode : 0; + bool defaultPrevented = aExtraFlags.mDefaultPrevented; + for (uint32_t cnt = 0; cnt < longestLength; cnt++) { + uint16_t uniChar, shiftedChar, unshiftedChar; + uniChar = shiftedChar = unshiftedChar = 0; + ModifierKeyState modKeyState(mModKeyState); + if (skipUniChars <= cnt) { + if (cnt - skipUniChars < inputtingChars.mLength) { + // If key in combination with Alt and/or Ctrl produces a different + // character than without them then do not report these flags + // because it is separate keyboard layout shift state. If dead-key + // and base character does not produce a valid composite character + // then both produced dead-key character and following base + // character may have different modifier flags, too. + modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | + MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); + modKeyState.Set(inputtingChars.mModifiers[cnt - skipUniChars]); + } + uniChar = inputtingChars.mChars[cnt - skipUniChars]; + } + if (skipShiftedChars <= cnt) + shiftedChar = shiftedChars.mChars[cnt - skipShiftedChars]; + if (skipUnshiftedChars <= cnt) + unshiftedChar = unshiftedChars.mChars[cnt - skipUnshiftedChars]; + nsAutoTArray altArray; + + if (shiftedChar || unshiftedChar) { + nsAlternativeCharCode chars(unshiftedChar, shiftedChar); + altArray.AppendElement(chars); + } + if (cnt == longestLength - 1) { + if (unshiftedLatinChar || shiftedLatinChar) { + nsAlternativeCharCode chars(unshiftedLatinChar, shiftedLatinChar); + altArray.AppendElement(chars); + } + + // Typically, following virtual keycodes are used for a key which can + // input the character. However, these keycodes are also used for + // other keys on some keyboard layout. E.g., in spite of Shift+'1' + // inputs '+' on Thai keyboard layout, a key which is at '=/+' + // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications + // handle it as '+' key if Ctrl key is pressed. + PRUnichar charForOEMKeyCode = 0; + switch (mVirtualKeyCode) { + case VK_OEM_PLUS: charForOEMKeyCode = '+'; break; + case VK_OEM_COMMA: charForOEMKeyCode = ','; break; + case VK_OEM_MINUS: charForOEMKeyCode = '-'; break; + case VK_OEM_PERIOD: charForOEMKeyCode = '.'; break; + } + if (charForOEMKeyCode && + charForOEMKeyCode != unshiftedChars.mChars[0] && + charForOEMKeyCode != shiftedChars.mChars[0] && + charForOEMKeyCode != unshiftedLatinChar && + charForOEMKeyCode != shiftedLatinChar) { + nsAlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode); + altArray.AppendElement(OEMChars); + } + } + + nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget); + keypressEvent.mFlags.Union(aExtraFlags); + keypressEvent.charCode = uniChar; + keypressEvent.alternativeCharCodes.AppendElements(altArray); + InitKeyEvent(keypressEvent, modKeyState); + defaultPrevented = (DispatchKeyEvent(keypressEvent) || defaultPrevented); + } + + return defaultPrevented; +} + +bool +NativeKey::DispatchKeyPressEventForFollowingCharMessage( + const EventFlags& aExtraFlags) const +{ + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + + const MSG& msg = RemoveFollowingCharMessage(); + if (mIsFakeCharMsg) { + if (msg.message == WM_DEADCHAR) { + return aExtraFlags.mDefaultPrevented; + } +#ifdef DEBUG + if (mIsPrintableKey) { + nsPrintfCString log( + "mOriginalVirtualKeyCode=0x%02X, mCommittedCharsAndModifiers={ " + "mChars=[ 0x%04X, 0x%04X, 0x%04X, 0x%04X, 0x%04X ], mLength=%d }, " + "wParam=0x%04X", + mOriginalVirtualKeyCode, mCommittedCharsAndModifiers.mChars[0], + mCommittedCharsAndModifiers.mChars[1], + mCommittedCharsAndModifiers.mChars[2], + mCommittedCharsAndModifiers.mChars[3], + mCommittedCharsAndModifiers.mChars[4], + mCommittedCharsAndModifiers.mLength, msg.wParam); + if (mCommittedCharsAndModifiers.IsEmpty()) { + log.Insert("length is zero: ", 0); + NS_ERROR(log.get()); + NS_ABORT(); + } else if (mCommittedCharsAndModifiers.mChars[0] != msg.wParam) { + log.Insert("character mismatch: ", 0); + NS_ERROR(log.get()); + NS_ABORT(); + } + } +#endif // #ifdef DEBUG + return HandleCharMessage(msg, nullptr, &aExtraFlags); + } + + // If prevent default set for keydown, do same for keypress + if (msg.message == WM_DEADCHAR) { + bool defaultPrevented = aExtraFlags.mDefaultPrevented; + if (mWidget->PluginHasFocus()) { + // We need to send the removed message to focused plug-in. + defaultPrevented = mWidget->DispatchPluginEvent(msg) || defaultPrevented; + } + return defaultPrevented; + } + + bool defaultPrevented = (HandleCharMessage(msg, nullptr, &aExtraFlags) || + aExtraFlags.mDefaultPrevented); + // If a syschar keypress wasn't processed, Windows may want to + // handle it to activate a native menu. + if (!defaultPrevented && msg.message == WM_SYSCHAR) { + ::DefWindowProcW(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + return defaultPrevented; +} + /***************************************************************************** * mozilla::widget::KeyboardLayout *****************************************************************************/ +KeyboardLayout* KeyboardLayout::sInstance = nullptr; +nsIIdleServiceInternal* KeyboardLayout::sIdleService = nullptr; + +// static +KeyboardLayout* +KeyboardLayout::GetInstance() +{ + if (!sInstance) { + sInstance = new KeyboardLayout(); + nsCOMPtr idleService = + do_GetService("@mozilla.org/widget/idleservice;1"); + // The refcount will be decreased at shutting down. + sIdleService = idleService.forget().get(); + } + return sInstance; +} + +// static +void +KeyboardLayout::Shutdown() +{ + delete sInstance; + sInstance = nullptr; + NS_IF_RELEASE(sIdleService); +} + +// static +void +KeyboardLayout::NotifyIdleServiceOfUserActivity() +{ + sIdleService->ResetIdleTimeOut(0); +} + KeyboardLayout::KeyboardLayout() : - mKeyboardLayout(0), mPendingKeyboardLayout(0) + mKeyboardLayout(0), mIsOverridden(false), + mIsPendingToRestoreKeyboardLayout(false) { mDeadKeyTableListHead = nullptr; - // Note: Don't call LoadLayout from here. Because an instance of this class - // can be static. In that case, we cannot use any services in LoadLayout, - // e.g., pref service. + // NOTE: LoadLayout() should be called via OnLayoutChange(). } KeyboardLayout::~KeyboardLayout() @@ -649,6 +1411,13 @@ KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey) return GetKeyIndex(aVirtualKey) >= 0; } +WORD +KeyboardLayout::ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const +{ + return static_cast( + ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC, GetLayout())); +} + bool KeyboardLayout::IsDeadKey(uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const @@ -666,11 +1435,11 @@ void KeyboardLayout::InitNativeKey(NativeKey& aNativeKey, const ModifierKeyState& aModKeyState) { - if (mPendingKeyboardLayout) { - LoadLayout(mPendingKeyboardLayout); + if (mIsPendingToRestoreKeyboardLayout) { + LoadLayout(::GetKeyboardLayout(0)); } - uint8_t virtualKey = aNativeKey.GetOriginalVirtualKeyCode(); + uint8_t virtualKey = aNativeKey.mOriginalVirtualKeyCode; int32_t virtualKeyIndex = GetKeyIndex(virtualKey); if (virtualKeyIndex < 0) { @@ -769,14 +1538,9 @@ KeyboardLayout::GetUniCharsAndModifiers( } void -KeyboardLayout::LoadLayout(HKL aLayout, bool aLoadLater) +KeyboardLayout::LoadLayout(HKL aLayout) { - if (aLoadLater) { - mPendingKeyboardLayout = aLayout; - return; - } - - mPendingKeyboardLayout = 0; + mIsPendingToRestoreKeyboardLayout = false; if (mKeyboardLayout == aLayout) { return; @@ -1298,6 +2062,202 @@ KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const } } +nsresult +KeyboardLayout::SynthesizeNativeKeyEvent(nsWindowBase* aWidget, + int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters) +{ + UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, NULL); + NS_ASSERTION(keyboardLayoutListCount > 0, + "One keyboard layout must be installed at least"); + HKL keyboardLayoutListBuff[50]; + HKL* keyboardLayoutList = + keyboardLayoutListCount < 50 ? keyboardLayoutListBuff : + new HKL[keyboardLayoutListCount]; + keyboardLayoutListCount = + ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList); + NS_ASSERTION(keyboardLayoutListCount > 0, + "Failed to get all keyboard layouts installed on the system"); + + nsPrintfCString layoutName("%08x", aNativeKeyboardLayout); + HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL); + if (loadedLayout == NULL) { + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } + return NS_ERROR_NOT_AVAILABLE; + } + + // Setup clean key state and load desired layout + BYTE originalKbdState[256]; + ::GetKeyboardState(originalKbdState); + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + // This changes the state of the keyboard for the current thread only, + // and we'll restore it soon, so this should be OK. + ::SetKeyboardState(kbdState); + + OverrideLayout(loadedLayout); + + uint8_t argumentKeySpecific = 0; + switch (aNativeKeyCode) { + case VK_SHIFT: + aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R); + argumentKeySpecific = VK_LSHIFT; + break; + case VK_LSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_L; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_SHIFT; + break; + case VK_RSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_R; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_SHIFT; + break; + case VK_CONTROL: + aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R); + argumentKeySpecific = VK_LCONTROL; + break; + case VK_LCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_L; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_CONTROL; + break; + case VK_RCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_R; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_CONTROL; + break; + case VK_MENU: + aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R); + argumentKeySpecific = VK_LMENU; + break; + case VK_LMENU: + aModifierFlags &= ~nsIWidget::ALT_L; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_MENU; + break; + case VK_RMENU: + aModifierFlags &= ~nsIWidget::ALT_R; + argumentKeySpecific = aNativeKeyCode; + aNativeKeyCode = VK_MENU; + break; + case VK_CAPITAL: + aModifierFlags &= ~nsIWidget::CAPS_LOCK; + argumentKeySpecific = VK_CAPITAL; + break; + case VK_NUMLOCK: + aModifierFlags &= ~nsIWidget::NUM_LOCK; + argumentKeySpecific = VK_NUMLOCK; + break; + } + + nsAutoTArray keySequence; + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags); + NS_ASSERTION(aNativeKeyCode >= 0 && aNativeKeyCode < 256, + "Native VK key code out of range"); + keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); + + // Simulate the pressing of each modifier key and then the real key + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + kbdState[key] = 0x81; // key is down and toggled on if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0x81; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + UINT scanCode = ComputeScanCodeForVirtualKeyCode( + argumentKeySpecific ? argumentKeySpecific : aNativeKeyCode); + LPARAM lParam = static_cast(scanCode << 16); + // Add extended key flag to the lParam for right control key and right alt + // key. + if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) { + lParam |= 0x1000000; + } + MSG keyDownMsg = WinUtils::InitMSG(WM_KEYDOWN, key, lParam, + aWidget->GetWindowHandle()); + if (i == keySequence.Length() - 1) { + bool makeDeadCharMsg = + (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty()); + nsAutoString chars(aCharacters); + if (makeDeadCharMsg) { + UniCharsAndModifiers deadChars = + GetUniCharsAndModifiers(key, modKeyState); + chars = deadChars.ToString(); + NS_ASSERTION(chars.Length() == 1, + "Dead char must be only one character"); + } + if (chars.IsEmpty()) { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } else { + NativeKey::FakeCharMsg fakeMsgForKeyDown = { chars.CharAt(0), scanCode, + makeDeadCharMsg }; + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, + &fakeMsgForKeyDown); + nativeKey.HandleKeyDownMessage(); + for (uint32_t j = 1; j < chars.Length(); j++) { + NativeKey::FakeCharMsg fakeMsgForChar = { chars.CharAt(j), scanCode, + false }; + MSG charMsg = fakeMsgForChar.GetCharMsg(aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, charMsg, modKeyState); + nativeKey.HandleCharMessage(charMsg); + } + } + } else { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } + } + for (uint32_t i = keySequence.Length(); i > 0; --i) { + uint8_t key = keySequence[i - 1].mGeneral; + uint8_t keySpecific = keySequence[i - 1].mSpecific; + kbdState[key] = 0; // key is up and toggled off if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + UINT scanCode = ComputeScanCodeForVirtualKeyCode( + argumentKeySpecific ? argumentKeySpecific : aNativeKeyCode); + LPARAM lParam = static_cast(scanCode << 16); + // Add extended key flag to the lParam for right control key and right alt + // key. + if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) { + lParam |= 0x1000000; + } + MSG keyUpMsg = WinUtils::InitMSG(WM_KEYUP, key, lParam, + aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, keyUpMsg, modKeyState); + nativeKey.HandleKeyUpMessage(); + } + + // Restore old key state and layout + ::SetKeyboardState(originalKbdState); + RestoreLayout(); + + // Don't unload the layout if it's installed actually. + for (uint32_t i = 0; i < keyboardLayoutListCount; i++) { + if (keyboardLayoutList[i] == loadedLayout) { + loadedLayout = 0; + break; + } + } + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } + if (loadedLayout) { + ::UnloadKeyboardLayout(loadedLayout); + } + return NS_OK; +} + /***************************************************************************** * mozilla::widget::DeadKeyTable *****************************************************************************/ @@ -1320,6 +2280,35 @@ DeadKeyTable::GetCompositeChar(PRUnichar aBaseChar) const return 0; } +/***************************************************************************** + * mozilla::widget::RedirectedKeyDownMessage + *****************************************************************************/ + +MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg; +bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false; + +// static +bool +RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) +{ + return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) && + (sRedirectedKeyDownMsg.message == aMsg.message && + WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) == + WinUtils::GetScanCode(aMsg.lParam)); +} + +// static +void +RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) +{ + MSG msg; + if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD) && + (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { + WinUtils::GetMessage(&msg, aWnd, msg.message, msg.message); + } +} + } // namespace widget } // namespace mozilla diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h index fd8ba3ee57aa..bd29933ebe32 100644 --- a/widget/windows/KeyboardLayout.h +++ b/widget/windows/KeyboardLayout.h @@ -7,8 +7,11 @@ #define KeyboardLayout_h__ #include "nscore.h" +#include "nsAutoPtr.h" #include "nsEvent.h" #include "nsString.h" +#include "nsWindowBase.h" +#include "nsWindowDefs.h " #include #define NS_NUM_OF_KEYS 68 @@ -32,12 +35,23 @@ #define VK_OEM_102 0xE2 #define VK_OEM_CLEAR 0xFE -class nsWindow; +class nsIIdleServiceInternal; struct nsModifierKeyState; namespace mozilla { namespace widget { +static const uint32_t sModifierKeyMap[][3] = { + { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 }, + { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 }, + { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT }, + { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT }, + { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL }, + { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL }, + { nsIWidget::ALT_L, VK_MENU, VK_LMENU }, + { nsIWidget::ALT_R, VK_MENU, VK_RMENU } +}; + class KeyboardLayout; class ModifierKeyState { @@ -138,6 +152,7 @@ struct UniCharsAndModifiers */ void Append(PRUnichar aUniChar, Modifiers aModifiers); void Clear() { mLength = 0; } + bool IsEmpty() const { return !mLength; } void FillModifiers(Modifiers aModifiers); @@ -272,43 +287,71 @@ public: UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const; }; -class NativeKey { +class MOZ_STACK_CLASS NativeKey +{ + friend class KeyboardLayout; + public: - NativeKey() : - mDOMKeyCode(0), mKeyNameIndex(KEY_NAME_INDEX_Unidentified), - mMessage(0), mVirtualKeyCode(0), mOriginalVirtualKeyCode(0), - mScanCode(0), mIsExtended(false) + struct FakeCharMsg { - } + UINT mCharCode; + UINT mScanCode; + bool mIsDeadKey; - NativeKey(const KeyboardLayout& aKeyboardLayout, - nsWindow* aWindow, - const MSG& aKeyOrCharMessage); + MSG GetCharMsg(HWND aWnd) const + { + MSG msg; + msg.hwnd = aWnd; + msg.message = mIsDeadKey ? WM_DEADCHAR : WM_CHAR; + msg.wParam = static_cast(mCharCode); + msg.lParam = static_cast(mScanCode); + msg.time = 0; + msg.pt.x = msg.pt.y = 0; + return msg; + } + }; - uint32_t GetDOMKeyCode() const { return mDOMKeyCode; } - KeyNameIndex GetKeyNameIndex() const { return mKeyNameIndex; } - const UniCharsAndModifiers& GetCommittedCharsAndModifiers() const - { - return mCommittedCharsAndModifiers; - } + NativeKey(nsWindowBase* aWidget, + const MSG& aKeyOrCharMessage, + const ModifierKeyState& aModKeyState, + const FakeCharMsg* aFakeCharMsg = nullptr); - // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*. - uint32_t GetKeyLocation() const; - UINT GetMessage() const { return mMessage; } - bool IsKeyDownMessage() const - { - return (mMessage == WM_KEYDOWN || mMessage == WM_SYSKEYDOWN); - } - WORD GetScanCode() const { return mScanCode; } - uint8_t GetVirtualKeyCode() const { return mVirtualKeyCode; } - uint8_t GetOriginalVirtualKeyCode() const { return mOriginalVirtualKeyCode; } + /** + * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be + * initialized with WM_KEYDOWN or WM_SYSKEYDOWN. + * Returns true if dispatched keydown event or keypress event is consumed. + * Otherwise, false. + */ + bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched = nullptr, + const EventFlags* aExtraFlags = nullptr) const; + + /** + * Handles keyup message. Returns true if the event is consumed. + * Otherwise, false. + */ + bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const; private: + nsRefPtr mWidget; + HKL mKeyboardLayout; + MSG mMsg; + // mCharMsg stores WM_*CHAR message following WM_*KEYDOWN message. + // If mMsg isn't WM_*KEYDOWN message or WM_*KEYDOWN but there is no following + // WM_*CHAR message, the message member is 0. + MSG mCharMsg; + uint32_t mDOMKeyCode; KeyNameIndex mKeyNameIndex; - // The message which the instance was initialized with. - UINT mMessage; + ModifierKeyState mModKeyState; // mVirtualKeyCode distinguishes left key or right key of modifier key. uint8_t mVirtualKeyCode; @@ -324,14 +367,124 @@ private: WORD mScanCode; bool mIsExtended; + bool mIsDeadKey; + // mIsPrintableKey is true if the key may be a printable key without + // any modifier keys. Otherwise, false. + // Please note that the event may not cause any text input even if this + // is true. E.g., it might be dead key state or Ctrl key may be pressed. + bool mIsPrintableKey; + bool mIsFakeCharMsg; + + NativeKey() + { + MOZ_NOT_REACHED("The default constructor of NativeKey isn't available"); + } UINT GetScanCodeWithExtendedFlag() const; - friend class KeyboardLayout; + // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*. + uint32_t GetKeyLocation() const; + + /** + * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes + * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this + * returns true, the caller needs to be careful for processing the messages. + */ + bool IsIMEDoingKakuteiUndo() const; + + /* + * Dispatches a plugin event after the specified message is removed. + */ + void RemoveMessageAndDispatchPluginEvent(UINT aFirstMsg, UINT aLastMsg) const; + + bool IsKeyDownMessage() const + { + return (mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + } + bool IsFollowedByCharMessage() const + { + MOZ_ASSERT(mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN); + return (mCharMsg.message != 0); + } + const MSG& RemoveFollowingCharMessage() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK. + */ + uint8_t ComputeVirtualKeyCodeFromScanCode() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX. + */ + uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR. + */ + PRUnichar ComputeUnicharFromScanCode() const; + + /** + * Initializes the aKeyEvent with the information stored in the instance. + */ + void InitKeyEvent(nsKeyEvent& aKeyEvent, + const ModifierKeyState& aModKeyState) const; + void InitKeyEvent(nsKeyEvent& aKeyEvent) const + { + InitKeyEvent(aKeyEvent, mModKeyState); + } + + /** + * Dispatches the key event. Returns true if the event is consumed. + * Otherwise, false. + */ + bool DispatchKeyEvent(nsKeyEvent& aKeyEvent, + const MSG* aMsgSentToPlugin = nullptr) const; + + /** + * DispatchKeyPressEventsWithKeyboardLayout() dispatches keypress event(s) + * with the information provided by KeyboardLayout class. + */ + bool DispatchKeyPressEventsWithKeyboardLayout( + const EventFlags& aExtraFlags) const; + + /** + * Dispatches keypress events after removing WM_*CHAR messages for the + * WM_*KEYDOWN message. + * Returns true if the dispatched keypress event is consumed. Otherwise, + * false. + */ + bool DispatchKeyPressEventsAndDiscardsCharMessages( + const EventFlags& aExtraFlags) const; + + /** + * DispatchKeyPressEventForFollowingCharMessage() dispatches keypress event + * for following WM_*CHAR message. + * Returns true if the event is consumed. Otherwise, false. + */ + bool DispatchKeyPressEventForFollowingCharMessage( + const EventFlags& aExtraFlags) const; + + /** + * Checkes whether the key event down message is handled without following + * WM_CHAR messages. For example, if following WM_CHAR message indicates + * control character input, the WM_CHAR message is unclear whether it's + * caused by a printable key with Ctrl or just a function key such as Enter + * or Backspace. + */ + bool NeedsToHandleWithoutFollowingCharMessages() const; }; class KeyboardLayout { + friend class NativeKey; + +private: + KeyboardLayout(); + ~KeyboardLayout(); + + static KeyboardLayout* sInstance; + static nsIIdleServiceInternal* sIdleService; + struct DeadKeyTableListEntry { DeadKeyTableListEntry* next; @@ -339,13 +492,15 @@ class KeyboardLayout }; HKL mKeyboardLayout; - HKL mPendingKeyboardLayout; VirtualKey mVirtualKeys[NS_NUM_OF_KEYS]; DeadKeyTableListEntry* mDeadKeyTableListHead; int32_t mActiveDeadKey; // -1 = no active dead-key VirtualKey::ShiftState mDeadKeyShiftState; + bool mIsOverridden : 1; + bool mIsPendingToRestoreKeyboardLayout : 1; + static inline int32_t GetKeyIndex(uint8_t aVirtualKey); static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2, void* aData); @@ -363,9 +518,25 @@ class KeyboardLayout uint32_t aEntries); void ReleaseDeadKeyTables(); + /** + * Loads the specified keyboard layout. This method always clear the dead key + * state. + */ + void LoadLayout(HKL aLayout); + + /** + * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or + * WM_KEYUP. This method is stateful. This saves current dead key state at + * WM_KEYDOWN. Additionally, computes current inputted character(s) and set + * them to the aNativeKey. + */ + void InitNativeKey(NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState); + public: - KeyboardLayout(); - ~KeyboardLayout(); + static KeyboardLayout* GetInstance(); + static void Shutdown(); + static void NotifyIdleServiceOfUserActivity(); static bool IsPrintableCharKey(uint8_t aVirtualKey); @@ -385,19 +556,34 @@ public: const ModifierKeyState& aModKeyState) const; /** - * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or - * WM_KEYUP. This method is stateful. This saves current dead key state at - * WM_KEYDOWN. Additionally, computes current inputted character(s) and set - * them to the aNativeKey. + * OnLayoutChange() must be called before the first keydown message is + * received. LoadLayout() changes the keyboard state, that causes breaking + * dead key state. Therefore, we need to load the layout before the first + * keydown message. */ - void InitNativeKey(NativeKey& aNativeKey, - const ModifierKeyState& aModKeyState); + void OnLayoutChange(HKL aKeyboardLayout) + { + MOZ_ASSERT(!mIsOverridden); + LoadLayout(aKeyboardLayout); + } /** - * LoadLayout() loads the keyboard layout. If aLoadLater is true, - * it will be done when OnKeyDown() is called. + * OverrideLayout() loads the specified keyboard layout. */ - void LoadLayout(HKL aLayout, bool aLoadLater = false); + void OverrideLayout(HKL aLayout) + { + mIsOverridden = true; + LoadLayout(aLayout); + } + + /** + * RestoreLayout() loads the current keyboard layout of the thread. + */ + void RestoreLayout() + { + mIsOverridden = false; + mIsPendingToRestoreKeyboardLayout = true; + } uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const; @@ -409,8 +595,102 @@ public: HKL GetLayout() const { - return mPendingKeyboardLayout ? mPendingKeyboardLayout : mKeyboardLayout; + return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0) : + mKeyboardLayout; } + + /** + * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC. + */ + WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const; + + /** + * Implementation of nsIWidget::SynthesizeNativeKeyEvent(). + */ + nsresult SynthesizeNativeKeyEvent(nsWindowBase* aWidget, + int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters); +}; + +class RedirectedKeyDownMessageManager +{ +public: + /* + * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is + * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent() + * prevents to dispatch NS_KEY_DOWN event because it has been dispatched + * before the message was redirected. However, in some cases, WM_*KEYDOWN + * message handler may not handle actually. Then, the message handler needs + * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR + * message for the redirected keydown message. AutoFlusher class is a helper + * class for doing it. This must be created in the stack. + */ + class MOZ_STACK_CLASS AutoFlusher MOZ_FINAL + { + public: + AutoFlusher(nsWindowBase* aWidget, const MSG &aMsg) : + mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)), + mWidget(aWidget), mMsg(aMsg) + { + } + + ~AutoFlusher() + { + if (mCancel) { + return; + } + // Prevent unnecessary keypress event + if (!mWidget->Destroyed()) { + RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd); + } + // Foreget the redirected message + RedirectedKeyDownMessageManager::Forget(); + } + + void Cancel() { mCancel = true; } + + private: + bool mCancel; + nsRefPtr mWidget; + const MSG &mMsg; + }; + + static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented) + { + sRedirectedKeyDownMsg = aMsg; + sDefaultPreventedOfRedirectedMsg = aDefualtPrevented; + } + + static void Forget() + { + sRedirectedKeyDownMsg.message = WM_NULL; + } + + static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; } + static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; } + + static bool IsRedirectedMessage(const MSG& aMsg); + + /** + * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM + * message handler. If there is no WM_(SYS)CHAR message for it, this + * method does nothing. + * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is + * called in message loop. So, WM_(SYS)KEYDOWN message should have + * WM_(SYS)CHAR message in the queue if the keydown event causes character + * input. + */ + static void RemoveNextCharMessage(HWND aWnd); + +private: + // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which + // is reirected with SendInput() API by + // widget::NativeKey::DispatchKeyDownAndKeyPressEvent() + static MSG sRedirectedKeyDownMsg; + static bool sDefaultPreventedOfRedirectedMsg; }; } // namespace widget diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp index 44db84756418..f1234cc10376 100644 --- a/widget/windows/WinIMEHandler.cpp +++ b/widget/windows/WinIMEHandler.cpp @@ -83,21 +83,6 @@ IMEHandler::GetNativeData(uint32_t aDataType) #endif // #ifdef NS_ENABLE_TSF #else } -// static -bool -IMEHandler::IsIMEEnabled(const InputContext& aInputContext) -{ - return IsIMEEnabled(aInputContext.mIMEState.mEnabled); -} - -// static -bool -IMEHandler::IsIMEEnabled(IMEState::Enabled aIMEState) -{ - return (aIMEState == mozilla::widget::IMEState::ENABLED || - aIMEState == mozilla::widget::IMEState::PLUGIN); -} - // static bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) @@ -282,7 +267,7 @@ IMEHandler::SetInputContext(nsWindow* aWindow, // Assume that SetInputContext() is called only when aWindow has focus. sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); - bool enable = IsIMEEnabled(aInputContext); + bool enable = WinUtils::IsIMEEnabled(aInputContext); bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); bool open = (adjustOpenState && @@ -372,41 +357,6 @@ IMEHandler::CurrentKeyboardLayoutHasIME() } #endif // #ifdef DEBUG -// static -bool -IMEHandler::IsDoingKakuteiUndo(HWND aWnd) -{ - // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG: - // --------------------------------------------------------------------------- - // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1) - // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK - // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0) - // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF) - // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1) - // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001) - // --------------------------------------------------------------------------- - // This doesn't match usual key message pattern such as: - // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP - // See following bugs for the detail. - // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese) - // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English) - MSG startCompositionMsg, compositionMsg, charMsg; - return WinUtils::PeekMessage(&startCompositionMsg, aWnd, - WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, - PM_NOREMOVE | PM_NOYIELD) && - WinUtils::PeekMessage(&compositionMsg, aWnd, WM_IME_COMPOSITION, - WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) && - WinUtils::PeekMessage(&charMsg, aWnd, WM_CHAR, WM_CHAR, - PM_NOREMOVE | PM_NOYIELD) && - startCompositionMsg.wParam == 0x0 && - startCompositionMsg.lParam == 0x0 && - compositionMsg.wParam == 0x0 && - compositionMsg.lParam == 0x1BF && - charMsg.wParam == VK_BACK && charMsg.lParam == 0x1 && - startCompositionMsg.time <= compositionMsg.time && - compositionMsg.time <= charMsg.time; -} - // static void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow, diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h index d6e672574cd8..09e6eef6b8c8 100644 --- a/widget/windows/WinIMEHandler.h +++ b/widget/windows/WinIMEHandler.h @@ -37,12 +37,6 @@ public: */ static void* GetNativeData(uint32_t aDataType); - /** - * Returns true if the context or IME state is enabled. Otherwise, false. - */ - static bool IsIMEEnabled(const InputContext& aInputContext); - static bool IsIMEEnabled(IMEState::Enabled aIMEState); - /** * ProcessRawKeyMessage() message is called before calling TranslateMessage() * and DispatchMessage(). If this returns true, the message is consumed. @@ -112,13 +106,6 @@ public: */ static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext); - /** - * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes - * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this - * returns true, the caller needs to be careful for processing the messages. - */ - static bool IsDoingKakuteiUndo(HWND aWnd); - #ifdef DEBUG /** * Returns true when current keyboard layout has IME. Otherwise, false. diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp index 9e86edbd7b9b..be2e35995104 100644 --- a/widget/windows/WinMouseScrollHandler.cpp +++ b/widget/windows/WinMouseScrollHandler.cpp @@ -277,7 +277,7 @@ MouseScrollHandler::SynthesizeNativeMouseScrollEvent(nsWindow* aWindow, memset(kbdState, 0, sizeof(kbdState)); nsAutoTArray keySequence; - nsWindow::SetupKeyModifiersSequence(&keySequence, aModifierFlags); + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags); for (uint32_t i = 0; i < keySequence.Length(); ++i) { uint8_t key = keySequence[i].mGeneral; diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp index 42f83026cf47..e20de9be21d2 100644 --- a/widget/windows/WinUtils.cpp +++ b/widget/windows/WinUtils.cpp @@ -7,6 +7,7 @@ #include "WinUtils.h" #include "nsWindow.h" #include "nsWindowDefs.h" +#include "KeyboardLayout.h" #include "nsGUIEvent.h" #include "nsIDOMMouseEvent.h" #include "mozilla/Preferences.h" @@ -39,7 +40,7 @@ namespace widget { #ifdef MOZ_PLACES NS_IMPL_ISUPPORTS1(AsyncFaviconDataReady, nsIFaviconDataCallback) #endif - NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncWriteIconToDisk, nsIRunnable) + NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncEncodeAndWriteIcon, nsIRunnable) NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncDeleteIconFromDisk, nsIRunnable) NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncDeleteAllFaviconsFromDisk, nsIRunnable) @@ -416,12 +417,13 @@ WinUtils::GetMouseInputSource() /* static */ MSG -WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam) +WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) { MSG msg; msg.message = aMessage; msg.wParam = wParam; msg.lParam = lParam; + msg.hwnd = aWnd; return msg; } @@ -501,7 +503,6 @@ myDownloadObserver::OnDownloadComplete(nsIDownloader *downloader, return NS_OK; } - nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) { if (!mURLShortcut) { @@ -531,8 +532,6 @@ nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) return NS_OK; } - - NS_IMETHODIMP AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI, uint32_t aDataLen, @@ -555,112 +554,116 @@ AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI, rv = icoFile->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); + // Convert the obtained favicon data to an input stream + nsCOMPtr stream; + rv = NS_NewByteInputStream(getter_AddRefs(stream), + reinterpret_cast(aData), + aDataLen, + NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + // Decode the image from the format it was returned to us in (probably PNG) + nsAutoCString mimeTypeOfInputData; + mimeTypeOfInputData.AssignLiteral("image/vnd.microsoft.icon"); + nsCOMPtr container; + nsCOMPtr imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); + rv = imgtool->DecodeImageData(stream, aMimeType, + getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr imgFrame; + rv = container->GetFrame(imgIContainer::FRAME_FIRST, 0, getter_AddRefs(imgFrame)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr imageSurface; + gfxIntSize size; + if (mURLShortcut) { + imageSurface = + new gfxImageSurface(gfxIntSize(48, 48), + gfxImageSurface::ImageFormatARGB32); + gfxContext context(imageSurface); + context.SetOperator(gfxContext::OPERATOR_SOURCE); + context.SetColor(gfxRGBA(1, 1, 1, 1)); + context.Rectangle(gfxRect(0, 0, 48, 48)); + context.Fill(); + + context.Translate(gfxPoint(16, 16)); + context.SetOperator(gfxContext::OPERATOR_OVER); + context.DrawSurface(imgFrame, gfxSize(16, 16)); + size = imageSurface->GetSize(); + } else { + imageSurface = imgFrame->GetAsReadableARGB32ImageSurface(); + size.width = GetSystemMetrics(SM_CXSMICON); + size.height = GetSystemMetrics(SM_CYSMICON); + if (!size.width || !size.height) { + size.width = 16; + size.height = 16; + } + } + // Allocate a new buffer that we own and can use out of line in // another thread. Copy the favicon raw data into it. const fallible_t fallible = fallible_t(); - uint8_t *data = new (fallible) uint8_t[aDataLen]; + uint8_t *data = new (fallible) uint8_t[imageSurface->GetDataSize()]; if (!data) { return NS_ERROR_OUT_OF_MEMORY; } - memcpy(data, aData, aDataLen); + memcpy(data, imageSurface->Data(), imageSurface->GetDataSize()); - //AsyncWriteIconToDisk takes ownership of the heap allocated buffer. - nsCOMPtr event = new AsyncWriteIconToDisk(path, aMimeType, - data, - aDataLen, - mURLShortcut); + // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer + nsCOMPtr event = new AsyncEncodeAndWriteIcon(path, data, + imageSurface->GetDataSize(), + imageSurface->Stride(), + size.width, + size.height, + mURLShortcut); mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); return NS_OK; } #endif -// Warning: AsyncWriteIconToDisk assumes ownership of the aData buffer passed in -AsyncWriteIconToDisk::AsyncWriteIconToDisk(const nsAString &aIconPath, - const nsACString &aMimeTypeOfInputData, - uint8_t *aBuffer, - uint32_t aBufferLength, - const bool aURLShortcut): +// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed in +AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon(const nsAString &aIconPath, + uint8_t *aBuffer, + uint32_t aBufferLength, + uint32_t aStride, + uint32_t aWidth, + uint32_t aHeight, + const bool aURLShortcut) : mURLShortcut(aURLShortcut), mIconPath(aIconPath), - mMimeTypeOfInputData(aMimeTypeOfInputData), mBuffer(aBuffer), - mBufferLength(aBufferLength) - + mBufferLength(aBufferLength), + mStride(aStride), + mWidth(aWidth), + mHeight(aHeight) { } -NS_IMETHODIMP AsyncWriteIconToDisk::Run() +NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() { NS_PRECONDITION(!NS_IsMainThread(), "Should not be called on the main thread."); - // Convert the obtained favicon data to an input stream - nsCOMPtr stream; - nsresult rv = - NS_NewByteInputStream(getter_AddRefs(stream), - reinterpret_cast(mBuffer.get()), - mBufferLength, - NS_ASSIGNMENT_DEPEND); - NS_ENSURE_SUCCESS(rv, rv); - - // Decode the image from the format it was returned to us in (probably PNG) - nsCOMPtr container; - nsCOMPtr imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); - rv = imgtool->DecodeImageData(stream, mMimeTypeOfInputData, - getter_AddRefs(container)); - NS_ENSURE_SUCCESS(rv, rv); - // Get the recommended icon width and height, or if failure to obtain // these settings, fall back to 16x16 ICOs. These values can be different // if the user has a different DPI setting other than 100%. // Windows would scale the 16x16 icon themselves, but it's better // we let our ICO encoder do it. nsCOMPtr iconStream; - if (!mURLShortcut) { - int32_t systemIconWidth = GetSystemMetrics(SM_CXSMICON); - int32_t systemIconHeight = GetSystemMetrics(SM_CYSMICON); - if ((systemIconWidth == 0 || systemIconHeight == 0)) { - systemIconWidth = 16; - systemIconHeight = 16; - } - // Scale the image to the needed size and in ICO format - mMimeTypeOfInputData.AssignLiteral("image/vnd.microsoft.icon"); - rv = imgtool->EncodeScaledImage(container, mMimeTypeOfInputData, - systemIconWidth, - systemIconHeight, - EmptyString(), - getter_AddRefs(iconStream)); - } else { - nsRefPtr s; - rv = container->GetFrame(imgIContainer::FRAME_FIRST, 0, getter_AddRefs(s)); - NS_ENSURE_SUCCESS(rv, rv); - - gfxImageSurface* surface = - new gfxImageSurface(gfxIntSize(48, 48), - gfxImageSurface::ImageFormatARGB32); - gfxContext context(surface); - context.SetOperator(gfxContext::OPERATOR_SOURCE); - context.SetColor(gfxRGBA(1, 1, 1, 1)); - context.Rectangle(gfxRect(0, 0, 48, 48)); - context.Fill(); - - context.Translate(gfxPoint(16, 16)); - context.SetOperator(gfxContext::OPERATOR_OVER); - context.DrawSurface(s, gfxSize(16, 16)); - gfxIntSize size = surface->GetSize(); - - nsRefPtr encoder = - do_CreateInstance("@mozilla.org/image/encoder;2?" - "type=image/vnd.microsoft.icon"); - NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE); - rv = encoder->InitFromData(surface->Data(), surface->Stride() * size.height, - size.width, size.height, surface->Stride(), - imgIEncoder::INPUT_FORMAT_HOSTARGB, EmptyString()); - NS_ENSURE_SUCCESS(rv, rv); - CallQueryInterface(encoder.get(), getter_AddRefs(iconStream)); - if (!iconStream) { - return NS_ERROR_FAILURE; - } + nsRefPtr encoder = + do_CreateInstance("@mozilla.org/image/encoder;2?" + "type=image/vnd.microsoft.icon"); + NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE); + nsresult rv = encoder->InitFromData(mBuffer, mBufferLength, + mWidth, mHeight, + mStride, + imgIEncoder::INPUT_FORMAT_HOSTARGB, + EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + CallQueryInterface(encoder.get(), getter_AddRefs(iconStream)); + if (!iconStream) { + return NS_ERROR_FAILURE; } NS_ENSURE_SUCCESS(rv, rv); @@ -704,7 +707,7 @@ NS_IMETHODIMP AsyncWriteIconToDisk::Run() return rv; } -AsyncWriteIconToDisk::~AsyncWriteIconToDisk() +AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() { } @@ -1026,5 +1029,34 @@ WinUtils::ToIntRect(const RECT& aRect) aRect.bottom - aRect.top); } +/* static */ +bool +WinUtils::IsIMEEnabled(const InputContext& aInputContext) +{ + return IsIMEEnabled(aInputContext.mIMEState.mEnabled); +} + +/* static */ +bool +WinUtils::IsIMEEnabled(IMEState::Enabled aIMEState) +{ + return (aIMEState == IMEState::ENABLED || + aIMEState == IMEState::PLUGIN); +} + +/* static */ +void +WinUtils::SetupKeyModifiersSequence(nsTArray* aArray, + uint32_t aModifiers) +{ + for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) { + const uint32_t* map = sModifierKeyMap[i]; + if (aModifiers & map[0]) { + aArray->AppendElement(KeyPair(map[1], map[2])); + } + } +} + + } // namespace widget } // namespace mozilla diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h index b244cfe7b9d2..0a74dbcb8b1a 100644 --- a/widget/windows/WinUtils.h +++ b/widget/windows/WinUtils.h @@ -21,10 +21,12 @@ #endif #include "nsIDownloader.h" #include "nsIURI.h" +#include "nsIWidget.h" #include "mozilla/Attributes.h" class nsWindow; +struct KeyPair; namespace mozilla { namespace widget { @@ -161,7 +163,7 @@ public: * InitMSG() returns an MSG struct which was initialized by the params. * Don't trust the other members in the result. */ - static MSG InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam); + static MSG InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd); /** * GetScanCode() returns a scan code for the LPARAM of WM_KEYDOWN, WM_KEYUP, @@ -244,6 +246,19 @@ public: */ static nsIntRect ToIntRect(const RECT& aRect); + /** + * Returns true if the context or IME state is enabled. Otherwise, false. + */ + static bool IsIMEEnabled(const InputContext& aInputContext); + static bool IsIMEEnabled(IMEState::Enabled aIMEState); + + /** + * Returns modifier key array for aModifiers. This is for + * nsIWidget::SynthethizeNative*Event(). + */ + static void SetupKeyModifiersSequence(nsTArray* aArray, + uint32_t aModifiers); + private: typedef HRESULT (WINAPI * SHCreateItemFromParsingNamePtr)(PCWSTR pszPath, IBindCtx *pbc, @@ -278,28 +293,31 @@ private: /** * Asynchronously tries add the list to the build */ -class AsyncWriteIconToDisk : public nsIRunnable +class AsyncEncodeAndWriteIcon : public nsIRunnable { public: const bool mURLShortcut; NS_DECL_ISUPPORTS NS_DECL_NSIRUNNABLE - // Warning: AsyncWriteIconToDisk assumes ownership of the aData buffer passed in - AsyncWriteIconToDisk(const nsAString &aIconPath, - const nsACString &aMimeTypeOfInputData, - uint8_t *aData, - uint32_t aDataLen, - const bool aURLShortcut); - virtual ~AsyncWriteIconToDisk(); + // Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed in + AsyncEncodeAndWriteIcon(const nsAString &aIconPath, + uint8_t *aData, uint32_t aDataLen, uint32_t aStride, + uint32_t aWidth, uint32_t aHeight, + const bool aURLShortcut); + virtual ~AsyncEncodeAndWriteIcon(); private: nsAutoString mIconPath; nsAutoCString mMimeTypeOfInputData; nsAutoArrayPtr mBuffer; uint32_t mBufferLength; + uint32_t mStride; + uint32_t mWidth; + uint32_t mHeight; }; + class AsyncDeleteIconFromDisk : public nsIRunnable { public: diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp index d42bfc9c7d44..3eb1bc82180f 100644 --- a/widget/windows/nsWidgetFactory.cpp +++ b/widget/windows/nsWidgetFactory.cpp @@ -17,6 +17,7 @@ #include "nsScreenManagerWin.h" #include "nsSound.h" #include "WinMouseScrollHandler.h" +#include "KeyboardLayout.h" #include "GfxInfo.h" #include "nsToolkit.h" @@ -259,6 +260,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { static void nsWidgetWindowsModuleDtor() { + KeyboardLayout::Shutdown(); MouseScrollHandler::Shutdown(); nsLookAndFeel::Shutdown(); nsToolkit::Shutdown(); diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index c0ebbc5aba30..d3ed20f9435d 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -151,9 +151,6 @@ #include "nsIWinTaskbar.h" #define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" -// Windowless plugin support -#include "npapi.h" - #include "nsWindowDefs.h" #include "nsCrashOnException.h" @@ -234,8 +231,6 @@ const PRUnichar* kOOPPPluginFocusEventId = L"OOPP Plugin Focus Widget Event"; uint32_t nsWindow::sOOPPPluginFocusEvent = RegisterWindowMessageW(kOOPPPluginFocusEventId); -MSG nsWindow::sRedirectedKeyDown; - /************************************************************** * * SECTION: globals variables @@ -248,9 +243,6 @@ static const char *sScreenManagerContractID = "@mozilla.org/gfx/screenmana PRLogModuleInfo* gWindowsLog = nullptr; #endif -// Kbd layout. Used throughout character processing. -static KeyboardLayout gKbdLayout; - // Global used in Show window enumerations. static bool gWindowsVisible = false; @@ -356,7 +348,7 @@ nsWindow::nsWindow() : nsWindowBase() // Global app registration id for Win7 and up. See // WinTaskbar.cpp for details. mozilla::widget::WinTaskbar::RegisterAppUserModelID(); - gKbdLayout.LoadLayout(::GetKeyboardLayout(0)); + KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0)); IMEHandler::Initialize(); if (SUCCEEDED(::OleInitialize(NULL))) { sIsOleInitialized = TRUE; @@ -367,7 +359,7 @@ nsWindow::nsWindow() : nsWindowBase() nsUXThemeData::InitTitlebarInfo(); // Init theme data nsUXThemeData::UpdateNativeThemeInfo(); - ForgetRedirectedKeyDownMessage(); + RedirectedKeyDownMessageManager::Forget(); } // !sInstanceCount mIdleService = nullptr; @@ -3618,33 +3610,6 @@ bool nsWindow::DispatchWindowEvent(nsGUIEvent* event, nsEventStatus &aStatus) { return ConvertStatus(aStatus); } -void nsWindow::InitKeyEvent(nsKeyEvent& aKeyEvent, - const NativeKey& aNativeKey, - const ModifierKeyState &aModKeyState) -{ - nsIntPoint point(0, 0); - InitEvent(aKeyEvent, &point); - aKeyEvent.mKeyNameIndex = aNativeKey.GetKeyNameIndex(); - aKeyEvent.location = aNativeKey.GetKeyLocation(); - aModKeyState.InitInputEvent(aKeyEvent); -} - -bool nsWindow::DispatchKeyEvent(nsKeyEvent& aKeyEvent, - const MSG *aMsgSentToPlugin) -{ - UserActivity(); - - NPEvent pluginEvent; - if (aMsgSentToPlugin && PluginHasFocus()) { - pluginEvent.event = aMsgSentToPlugin->message; - pluginEvent.wParam = aMsgSentToPlugin->wParam; - pluginEvent.lParam = aMsgSentToPlugin->lParam; - aKeyEvent.pluginEvent = (void *)&pluginEvent; - } - - return DispatchWindowEvent(&aKeyEvent); -} - bool nsWindow::DispatchCommandEvent(uint32_t aEventCommand) { nsCOMPtr command; @@ -3763,51 +3728,19 @@ void nsWindow::DispatchPendingEvents() } } -// Deal with plugin events -bool nsWindow::DispatchPluginEvent(const MSG &aMsg) -{ - if (!PluginHasFocus()) - return false; - - nsPluginEvent event(true, NS_PLUGIN_INPUT_EVENT, this); - nsIntPoint point(0, 0); - InitEvent(event, &point); - NPEvent pluginEvent; - pluginEvent.event = aMsg.message; - pluginEvent.wParam = aMsg.wParam; - pluginEvent.lParam = aMsg.lParam; - event.pluginEvent = (void *)&pluginEvent; - event.retargetToFocusedDocument = true; - return DispatchWindowEvent(&event); -} - bool nsWindow::DispatchPluginEvent(UINT aMessage, WPARAM aWParam, LPARAM aLParam, bool aDispatchPendingEvents) { - bool ret = DispatchPluginEvent(WinUtils::InitMSG(aMessage, aWParam, aLParam)); + bool ret = nsWindowBase::DispatchPluginEvent( + WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd)); if (aDispatchPendingEvents) { DispatchPendingEvents(); } return ret; } -void nsWindow::RemoveMessageAndDispatchPluginEvent(UINT aFirstMsg, - UINT aLastMsg, nsFakeCharMessage* aFakeCharMessage) -{ - MSG msg; - if (aFakeCharMessage) { - if (aFirstMsg > WM_CHAR || aLastMsg < WM_CHAR) { - return; - } - msg = aFakeCharMessage->GetCharMessage(mWnd); - } else { - WinUtils::GetMessage(&msg, mWnd, aFirstMsg, aLastMsg); - } - DispatchPluginEvent(msg); -} - // Deal with all sort of mouse event bool nsWindow::DispatchMouseEvent(uint32_t aEventType, WPARAM wParam, LPARAM lParam, bool aIsContextMenuKey, @@ -4442,7 +4375,7 @@ nsWindow::ProcessMessageForPlugin(const MSG &aMsg, } if (!eventDispatched) - aCallDefWndProc = !DispatchPluginEvent(aMsg); + aCallDefWndProc = !nsWindowBase::DispatchPluginEvent(aMsg); DispatchPendingEvents(); return true; } @@ -4503,7 +4436,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, if (PluginHasFocus()) { bool callDefaultWndProc; - MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam); + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); if (ProcessMessageForPlugin(nativeMsg, aRetValue, callDefaultWndProc)) { return mWnd ? !callDefaultWndProc : true; } @@ -4798,7 +4731,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, case WM_SYSCHAR: case WM_CHAR: { - MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam); + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); result = ProcessCharMessage(nativeMsg, nullptr); DispatchPendingEvents(); } @@ -4807,7 +4740,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, case WM_SYSKEYUP: case WM_KEYUP: { - MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam); + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); nativeMsg.time = ::GetMessageTime(); result = ProcessKeyUpMessage(nativeMsg, nullptr); DispatchPendingEvents(); @@ -4817,7 +4750,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, case WM_SYSKEYDOWN: case WM_KEYDOWN: { - MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam); + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); result = ProcessKeyDownMessage(nativeMsg, nullptr); DispatchPendingEvents(); } @@ -5110,7 +5043,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, sJustGotDeactivate = true; if (mIsTopWidgetWindow) - mLastKeyboardLayout = gKbdLayout.GetLayout(); + mLastKeyboardLayout = KeyboardLayout::GetInstance()->GetLayout(); } else { StopFlashing(); @@ -5169,7 +5102,7 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, // If previous focused window isn't ours, it must have received the // redirected message. So, we should forget it. if (!WinUtils::IsOurProcessWindow(HWND(wParam))) { - ForgetRedirectedKeyDownMessage(); + RedirectedKeyDownMessageManager::Forget(); } if (sJustGotActivate) { DispatchFocusToTopLevelWindow(true); @@ -5195,7 +5128,9 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, break; case WM_INPUTLANGCHANGE: - result = OnInputLangChange((HKL)lParam); + KeyboardLayout::GetInstance()-> + OnLayoutChange(reinterpret_cast(lParam)); + result = false; // always pass to child window break; case WM_DESTROYCLIPBOARD: @@ -5625,111 +5560,51 @@ void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) NS_WIDGET_WAKE_OBSERVER_TOPIC, nullptr); } -// RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM -// message handler. If there is no WM_(SYS)CHAR message for it, this -// method does nothing. -// NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is -// called in message loop. So, WM_(SYS)KEYDOWN message should have -// WM_(SYS)CHAR message in the queue if the keydown event causes character -// input. - -/* static */ -void nsWindow::RemoveNextCharMessage(HWND aWnd) -{ - MSG msg; - if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST, - PM_NOREMOVE | PM_NOYIELD) && - (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { - WinUtils::GetMessage(&msg, aWnd, msg.message, msg.message); - } -} - LRESULT nsWindow::ProcessCharMessage(const MSG &aMsg, bool *aEventDispatched) { - NS_PRECONDITION(aMsg.message == WM_CHAR || aMsg.message == WM_SYSCHAR, - "message is not keydown event"); - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("%s charCode=%d scanCode=%d\n", - aMsg.message == WM_SYSCHAR ? "WM_SYSCHAR" : "WM_CHAR", - aMsg.wParam, HIWORD(aMsg.lParam) & 0xFF)); - + if (IMEHandler::IsComposingOn(this)) { + IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION); + } // These must be checked here too as a lone WM_CHAR could be received - // if a child window didn't handle it (for example Alt+Space in a content window) + // if a child window didn't handle it (for example Alt+Space in a content + // window) ModifierKeyState modKeyState; - NativeKey nativeKey(gKbdLayout, this, aMsg); - gKbdLayout.InitNativeKey(nativeKey, modKeyState); - return OnChar(aMsg, nativeKey, modKeyState, aEventDispatched); + NativeKey nativeKey(this, aMsg, modKeyState); + return static_cast(nativeKey.HandleCharMessage(aMsg, + aEventDispatched)); } LRESULT nsWindow::ProcessKeyUpMessage(const MSG &aMsg, bool *aEventDispatched) { - NS_PRECONDITION(aMsg.message == WM_KEYUP || aMsg.message == WM_SYSKEYUP, - "message is not keydown event"); - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("%s VK=%d\n", aMsg.message == WM_SYSKEYDOWN ? - "WM_SYSKEYUP" : "WM_KEYUP", aMsg.wParam)); + if (IMEHandler::IsComposingOn(this)) { + return 0; + } ModifierKeyState modKeyState; - - // Note: the original code passed (HIWORD(lParam)) to OnKeyUp as - // scan code. However, this breaks Alt+Num pad input. - // MSDN states the following: - // Typically, ToAscii performs the translation based on the - // virtual-key code. In some cases, however, bit 15 of the - // uScanCode parameter may be used to distinguish between a key - // press and a key release. The scan code is used for - // translating ALT+number key combinations. - - // ignore [shift+]alt+space so the OS can handle it - if (modKeyState.IsAlt() && !modKeyState.IsControl() && - IS_VK_DOWN(NS_VK_SPACE)) { - return FALSE; - } - - if (!IMEHandler::IsComposingOn(this)) { - return OnKeyUp(aMsg, modKeyState, aEventDispatched); - } - - return 0; + NativeKey nativeKey(this, aMsg, modKeyState); + return static_cast(nativeKey.HandleKeyUpMessage(aEventDispatched)); } LRESULT nsWindow::ProcessKeyDownMessage(const MSG &aMsg, bool *aEventDispatched) { - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("%s VK=%d\n", aMsg.message == WM_SYSKEYDOWN ? - "WM_SYSKEYDOWN" : "WM_KEYDOWN", aMsg.wParam)); - NS_PRECONDITION(aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN, - "message is not keydown event"); - - // If this method doesn't call OnKeyDown(), this method must clean up the - // redirected message information itself. For more information, see above - // comment of AutoForgetRedirectedKeyDownMessage struct definition in - // nsWindow.h. - AutoForgetRedirectedKeyDownMessage forgetRedirectedMessage(this, aMsg); + // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method + // must clean up the redirected message information itself. For more + // information, see above comment of + // RedirectedKeyDownMessageManager::AutoFlusher class definition in + // KeyboardLayout.h. + RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg); ModifierKeyState modKeyState; - // Note: the original code passed (HIWORD(lParam)) to OnKeyDown as - // scan code. However, this breaks Alt+Num pad input. - // MSDN states the following: - // Typically, ToAscii performs the translation based on the - // virtual-key code. In some cases, however, bit 15 of the - // uScanCode parameter may be used to distinguish between a key - // press and a key release. The scan code is used for - // translating ALT+number key combinations. - - // ignore [shift+]alt+space so the OS can handle it - if (modKeyState.IsAlt() && !modKeyState.IsControl() && - IS_VK_DOWN(NS_VK_SPACE)) - return FALSE; - LRESULT result = 0; if (!IMEHandler::IsComposingOn(this)) { - result = OnKeyDown(aMsg, modKeyState, aEventDispatched, nullptr); - // OnKeyDown cleaned up the redirected message information itself, so, - // we should do nothing. - forgetRedirectedMessage.mCancel = true; + NativeKey nativeKey(this, aMsg, modKeyState); + result = + static_cast(nativeKey.HandleKeyDownMessage(aEventDispatched)); + // HandleKeyDownMessage cleaned up the redirected message information + // itself, so, we should do nothing. + redirectedMsgFlusher.Cancel(); } if (aMsg.wParam == VK_MENU || @@ -5761,186 +5636,10 @@ nsWindow::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, const nsAString& aCharacters, const nsAString& aUnmodifiedCharacters) { - UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, NULL); - NS_ASSERTION(keyboardLayoutListCount > 0, - "One keyboard layout must be installed at least"); - HKL keyboardLayoutListBuff[50]; - HKL* keyboardLayoutList = - keyboardLayoutListCount < 50 ? keyboardLayoutListBuff : - new HKL[keyboardLayoutListCount]; - keyboardLayoutListCount = - ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList); - NS_ASSERTION(keyboardLayoutListCount > 0, - "Failed to get all keyboard layouts installed on the system"); - - nsPrintfCString layoutName("%08x", aNativeKeyboardLayout); - HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL); - if (loadedLayout == NULL) { - if (keyboardLayoutListBuff != keyboardLayoutList) { - delete [] keyboardLayoutList; - } - return NS_ERROR_NOT_AVAILABLE; - } - - // Setup clean key state and load desired layout - BYTE originalKbdState[256]; - ::GetKeyboardState(originalKbdState); - BYTE kbdState[256]; - memset(kbdState, 0, sizeof(kbdState)); - // This changes the state of the keyboard for the current thread only, - // and we'll restore it soon, so this should be OK. - ::SetKeyboardState(kbdState); - HKL oldLayout = gKbdLayout.GetLayout(); - gKbdLayout.LoadLayout(loadedLayout); - - uint8_t argumentKeySpecific = 0; - switch (aNativeKeyCode) { - case VK_SHIFT: - aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R); - argumentKeySpecific = VK_LSHIFT; - break; - case VK_LSHIFT: - aModifierFlags &= ~nsIWidget::SHIFT_L; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_SHIFT; - break; - case VK_RSHIFT: - aModifierFlags &= ~nsIWidget::SHIFT_R; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_SHIFT; - break; - case VK_CONTROL: - aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R); - argumentKeySpecific = VK_LCONTROL; - break; - case VK_LCONTROL: - aModifierFlags &= ~nsIWidget::CTRL_L; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_CONTROL; - break; - case VK_RCONTROL: - aModifierFlags &= ~nsIWidget::CTRL_R; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_CONTROL; - break; - case VK_MENU: - aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R); - argumentKeySpecific = VK_LMENU; - break; - case VK_LMENU: - aModifierFlags &= ~nsIWidget::ALT_L; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_MENU; - break; - case VK_RMENU: - aModifierFlags &= ~nsIWidget::ALT_R; - argumentKeySpecific = aNativeKeyCode; - aNativeKeyCode = VK_MENU; - break; - case VK_CAPITAL: - aModifierFlags &= ~nsIWidget::CAPS_LOCK; - argumentKeySpecific = VK_CAPITAL; - break; - case VK_NUMLOCK: - aModifierFlags &= ~nsIWidget::NUM_LOCK; - argumentKeySpecific = VK_NUMLOCK; - break; - } - - nsAutoTArray keySequence; - SetupKeyModifiersSequence(&keySequence, aModifierFlags); - NS_ASSERTION(aNativeKeyCode >= 0 && aNativeKeyCode < 256, - "Native VK key code out of range"); - keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); - - // Simulate the pressing of each modifier key and then the real key - for (uint32_t i = 0; i < keySequence.Length(); ++i) { - uint8_t key = keySequence[i].mGeneral; - uint8_t keySpecific = keySequence[i].mSpecific; - kbdState[key] = 0x81; // key is down and toggled on if appropriate - if (keySpecific) { - kbdState[keySpecific] = 0x81; - } - ::SetKeyboardState(kbdState); - ModifierKeyState modKeyState; - UINT scanCode = ::MapVirtualKeyEx(argumentKeySpecific ? - argumentKeySpecific : aNativeKeyCode, - MAPVK_VK_TO_VSC, gKbdLayout.GetLayout()); - LPARAM lParam = static_cast(scanCode << 16); - // Add extended key flag to the lParam for right control key and right alt - // key. - if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) { - lParam |= 0x1000000; - } - MSG msg = WinUtils::InitMSG(WM_KEYDOWN, key, lParam); - if (i == keySequence.Length() - 1) { - bool makeDeadCharMessage = - gKbdLayout.IsDeadKey(key, modKeyState) && aCharacters.IsEmpty(); - nsAutoString chars(aCharacters); - if (makeDeadCharMessage) { - UniCharsAndModifiers deadChars = - gKbdLayout.GetUniCharsAndModifiers(key, modKeyState); - chars = deadChars.ToString(); - NS_ASSERTION(chars.Length() == 1, - "Dead char must be only one character"); - } - if (chars.IsEmpty()) { - OnKeyDown(msg, modKeyState, nullptr, nullptr); - } else { - nsFakeCharMessage fakeMsg = { chars.CharAt(0), scanCode, - makeDeadCharMessage }; - OnKeyDown(msg, modKeyState, nullptr, &fakeMsg); - for (uint32_t j = 1; j < chars.Length(); j++) { - nsFakeCharMessage fakeMsg = { chars.CharAt(j), scanCode, false }; - MSG msg = fakeMsg.GetCharMessage(mWnd); - NativeKey nativeKey(gKbdLayout, this, msg); - OnChar(msg, nativeKey, modKeyState, nullptr); - } - } - } else { - OnKeyDown(msg, modKeyState, nullptr, nullptr); - } - } - for (uint32_t i = keySequence.Length(); i > 0; --i) { - uint8_t key = keySequence[i - 1].mGeneral; - uint8_t keySpecific = keySequence[i - 1].mSpecific; - kbdState[key] = 0; // key is up and toggled off if appropriate - if (keySpecific) { - kbdState[keySpecific] = 0; - } - ::SetKeyboardState(kbdState); - ModifierKeyState modKeyState; - UINT scanCode = ::MapVirtualKeyEx(argumentKeySpecific ? - argumentKeySpecific : aNativeKeyCode, - MAPVK_VK_TO_VSC, gKbdLayout.GetLayout()); - LPARAM lParam = static_cast(scanCode << 16); - // Add extended key flag to the lParam for right control key and right alt - // key. - if (keySpecific == VK_RCONTROL || keySpecific == VK_RMENU) { - lParam |= 0x1000000; - } - MSG msg = WinUtils::InitMSG(WM_KEYUP, key, lParam); - OnKeyUp(msg, modKeyState, nullptr); - } - - // Restore old key state and layout - ::SetKeyboardState(originalKbdState); - gKbdLayout.LoadLayout(oldLayout, true); - - // Don't unload the layout if it's installed actually. - for (uint32_t i = 0; i < keyboardLayoutListCount; i++) { - if (keyboardLayoutList[i] == loadedLayout) { - loadedLayout = 0; - break; - } - } - if (keyboardLayoutListBuff != keyboardLayoutList) { - delete [] keyboardLayoutList; - } - if (loadedLayout) { - ::UnloadKeyboardLayout(loadedLayout); - } - return NS_OK; + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + return keyboardLayout->SynthesizeNativeKeyEvent( + this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, + aCharacters, aUnmodifiedCharacters); } nsresult @@ -5985,15 +5684,6 @@ nsWindow::SynthesizeNativeMouseScrollEvent(nsIntPoint aPoint, * **************************************************************/ -BOOL nsWindow::OnInputLangChange(HKL aHKL) -{ -#ifdef KE_DEBUG - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, ("OnInputLanguageChange\n")); -#endif - gKbdLayout.LoadLayout(aHKL); - return false; // always pass to child window -} - void nsWindow::OnWindowPosChanged(WINDOWPOS *wp, bool& result) { if (wp == nullptr) @@ -6442,511 +6132,6 @@ bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) return true; // Handled } -/* static */ -bool nsWindow::IsRedirectedKeyDownMessage(const MSG &aMsg) -{ - return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) && - (sRedirectedKeyDown.message == aMsg.message && - WinUtils::GetScanCode(sRedirectedKeyDown.lParam) == - WinUtils::GetScanCode(aMsg.lParam)); -} - -/** - * nsWindow::OnKeyDown peeks into the message queue and pulls out - * WM_CHAR messages for processing. During testing we don't want to - * mess with the real message queue. Instead we pass a - * pseudo-WM_CHAR-message using this structure, and OnKeyDown will use - * that as if it was in the message queue, and refrain from actually - * looking at or touching the message queue. - */ -LRESULT nsWindow::OnKeyDown(const MSG &aMsg, - const ModifierKeyState &aModKeyState, - bool *aEventDispatched, - nsFakeCharMessage* aFakeCharMessage) -{ - NativeKey nativeKey(gKbdLayout, this, aMsg); - gKbdLayout.InitNativeKey(nativeKey, aModKeyState); - UniCharsAndModifiers inputtingChars = - nativeKey.GetCommittedCharsAndModifiers(); - uint32_t DOMKeyCode = nativeKey.GetDOMKeyCode(); - -#ifdef DEBUG - //PR_LOG(gWindowsLog, PR_LOG_ALWAYS, ("In OnKeyDown virt: %d\n", DOMKeyCode)); -#endif - - static bool sRedirectedKeyDownEventPreventedDefault = false; - bool noDefault; - if (aFakeCharMessage || !IsRedirectedKeyDownMessage(aMsg)) { - bool isIMEEnabled = IMEHandler::IsIMEEnabled(mInputContext); - nsKeyEvent keydownEvent(true, NS_KEY_DOWN, this); - keydownEvent.keyCode = DOMKeyCode; - InitKeyEvent(keydownEvent, nativeKey, aModKeyState); - noDefault = DispatchKeyEvent(keydownEvent, &aMsg); - if (aEventDispatched) { - *aEventDispatched = true; - } - - // If IMC wasn't associated to the window but is associated it now (i.e., - // focus is moved from a non-editable editor to an editor by keydown - // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character - // inputting if IME is opened. But then, we should redirect the native - // keydown message to IME. - // However, note that if focus has been already moved to another - // application, we shouldn't redirect the message to it because the keydown - // message is processed by us, so, nobody shouldn't process it. - HWND focusedWnd = ::GetFocus(); - if (!noDefault && !aFakeCharMessage && focusedWnd && !PluginHasFocus() && - !isIMEEnabled && IMEHandler::IsIMEEnabled(mInputContext)) { - RemoveNextCharMessage(focusedWnd); - - INPUT keyinput; - keyinput.type = INPUT_KEYBOARD; - keyinput.ki.wVk = aMsg.wParam; - keyinput.ki.wScan = WinUtils::GetScanCode(aMsg.lParam); - keyinput.ki.dwFlags = KEYEVENTF_SCANCODE; - if (WinUtils::IsExtendedScanCode(aMsg.lParam)) { - keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; - } - keyinput.ki.time = 0; - keyinput.ki.dwExtraInfo = 0; - - sRedirectedKeyDownEventPreventedDefault = noDefault; - sRedirectedKeyDown = aMsg; - - ::SendInput(1, &keyinput, sizeof(keyinput)); - - // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN. - // If it's needed, it will be dispatched after next (redirected) - // WM_KEYDOWN. - return true; - } - - if (mOnDestroyCalled) { - // If this was destroyed by the keydown event handler, we shouldn't - // dispatch keypress event on this window. - return true; - } - } else { - noDefault = sRedirectedKeyDownEventPreventedDefault; - // If this is redirected keydown message, we have dispatched the keydown - // event already. - if (aEventDispatched) { - *aEventDispatched = true; - } - } - - ForgetRedirectedKeyDownMessage(); - - // If the key was processed by IME, we shouldn't dispatch keypress event. - if (aMsg.wParam == VK_PROCESSKEY) { - return noDefault; - } - - // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a keypress - // for almost all keys - switch (DOMKeyCode) { - case NS_VK_SHIFT: - case NS_VK_CONTROL: - case NS_VK_ALT: - case NS_VK_CAPS_LOCK: - case NS_VK_NUM_LOCK: - case NS_VK_SCROLL_LOCK: - case NS_VK_WIN: - return noDefault; - } - - UINT virtualKeyCode = nativeKey.GetOriginalVirtualKeyCode(); - bool isDeadKey = gKbdLayout.IsDeadKey(virtualKeyCode, aModKeyState); - EventFlags extraFlags; - extraFlags.mDefaultPrevented = noDefault; - MSG msg; - BOOL gotMsg = aFakeCharMessage || - WinUtils::PeekMessage(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, - PM_NOREMOVE | PM_NOYIELD); - // Enter and backspace are always handled here to avoid for example the - // confusion between ctrl-enter and ctrl-J. - if (DOMKeyCode == NS_VK_RETURN || DOMKeyCode == NS_VK_BACK || - ((aModKeyState.IsControl() || aModKeyState.IsAlt() || aModKeyState.IsWin()) - && !isDeadKey && KeyboardLayout::IsPrintableCharKey(virtualKeyCode))) - { - // Remove a possible WM_CHAR or WM_SYSCHAR messages from the message queue. - // They can be more than one because of: - // * Dead-keys not pairing with base character - // * Some keyboard layouts may map up to 4 characters to the single key - bool anyCharMessagesRemoved = false; - - if (aFakeCharMessage) { - RemoveMessageAndDispatchPluginEvent(WM_KEYFIRST, WM_KEYLAST, - aFakeCharMessage); - anyCharMessagesRemoved = true; - } else { - while (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) - { - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("%s charCode=%d scanCode=%d\n", msg.message == WM_SYSCHAR ? - "WM_SYSCHAR" : "WM_CHAR", - msg.wParam, HIWORD(msg.lParam) & 0xFF)); - RemoveMessageAndDispatchPluginEvent(WM_KEYFIRST, WM_KEYLAST); - anyCharMessagesRemoved = true; - - gotMsg = WinUtils::PeekMessage(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, - PM_NOREMOVE | PM_NOYIELD); - } - } - - if (!anyCharMessagesRemoved && DOMKeyCode == NS_VK_BACK && - IMEHandler::IsDoingKakuteiUndo(mWnd)) { - NS_ASSERTION(!aFakeCharMessage, - "We shouldn't be touching the real msg queue"); - RemoveMessageAndDispatchPluginEvent(WM_CHAR, WM_CHAR); - } - } - else if (gotMsg && - (aFakeCharMessage || - msg.message == WM_CHAR || msg.message == WM_SYSCHAR || msg.message == WM_DEADCHAR)) { - if (aFakeCharMessage) { - MSG msg = aFakeCharMessage->GetCharMessage(mWnd); - if (msg.message == WM_DEADCHAR) { - return false; - } -#ifdef DEBUG - if (KeyboardLayout::IsPrintableCharKey(virtualKeyCode)) { - nsPrintfCString log( - "virtualKeyCode=0x%02X, inputtingChar={ mChars=[ 0x%04X, 0x%04X, " - "0x%04X, 0x%04X, 0x%04X ], mLength=%d }, wParam=0x%04X", - virtualKeyCode, inputtingChars.mChars[0], inputtingChars.mChars[1], - inputtingChars.mChars[2], inputtingChars.mChars[3], - inputtingChars.mChars[4], inputtingChars.mLength, msg.wParam); - if (!inputtingChars.mLength) { - log.Insert("length is zero: ", 0); - NS_ERROR(log.get()); - NS_ABORT(); - } else if (inputtingChars.mChars[0] != msg.wParam) { - log.Insert("character mismatch: ", 0); - NS_ERROR(log.get()); - NS_ABORT(); - } - } -#endif // #ifdef DEBUG - return OnChar(msg, nativeKey, aModKeyState, nullptr, &extraFlags); - } - - // If prevent default set for keydown, do same for keypress - WinUtils::GetMessage(&msg, mWnd, msg.message, msg.message); - - if (msg.message == WM_DEADCHAR) { - if (!PluginHasFocus()) - return false; - - // We need to send the removed message to focused plug-in. - DispatchPluginEvent(msg); - return noDefault; - } - - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("%s charCode=%d scanCode=%d\n", - msg.message == WM_SYSCHAR ? "WM_SYSCHAR" : "WM_CHAR", - msg.wParam, HIWORD(msg.lParam) & 0xFF)); - - BOOL result = OnChar(msg, nativeKey, aModKeyState, nullptr, &extraFlags); - // If a syschar keypress wasn't processed, Windows may want to - // handle it to activate a native menu. - if (!result && msg.message == WM_SYSCHAR) - ::DefWindowProcW(mWnd, msg.message, msg.wParam, msg.lParam); - return result; - } - else if (!aModKeyState.IsControl() && !aModKeyState.IsAlt() && - !aModKeyState.IsWin() && - KeyboardLayout::IsPrintableCharKey(virtualKeyCode)) { - // If this is simple KeyDown event but next message is not WM_CHAR, - // this event may not input text, so we should ignore this event. - // See bug 314130. - return PluginHasFocus() && noDefault; - } - - if (isDeadKey) { - return PluginHasFocus() && noDefault; - } - - UniCharsAndModifiers shiftedChars; - UniCharsAndModifiers unshiftedChars; - uint32_t shiftedLatinChar = 0; - uint32_t unshiftedLatinChar = 0; - - if (!KeyboardLayout::IsPrintableCharKey(virtualKeyCode)) { - inputtingChars.Clear(); - } - - if (aModKeyState.IsControl() ^ aModKeyState.IsAlt()) { - widget::ModifierKeyState capsLockState( - aModKeyState.GetModifiers() & MODIFIER_CAPSLOCK); - unshiftedChars = - gKbdLayout.GetUniCharsAndModifiers(virtualKeyCode, capsLockState); - capsLockState.Set(MODIFIER_SHIFT); - shiftedChars = - gKbdLayout.GetUniCharsAndModifiers(virtualKeyCode, capsLockState); - - // The current keyboard cannot input alphabets or numerics, - // we should append them for Shortcut/Access keys. - // E.g., for Cyrillic keyboard layout. - capsLockState.Unset(MODIFIER_SHIFT); - WidgetUtils::GetLatinCharCodeForKeyCode(DOMKeyCode, - capsLockState.GetModifiers(), - &unshiftedLatinChar, - &shiftedLatinChar); - - // If the shiftedLatinChar isn't 0, the key code is NS_VK_[A-Z]. - if (shiftedLatinChar) { - // If the produced characters of the key on current keyboard layout - // are same as computed Latin characters, we shouldn't append the - // Latin characters to alternativeCharCode. - if (unshiftedLatinChar == unshiftedChars.mChars[0] && - shiftedLatinChar == shiftedChars.mChars[0]) { - shiftedLatinChar = unshiftedLatinChar = 0; - } - } else if (unshiftedLatinChar) { - // If the shiftedLatinChar is 0, the keyCode doesn't produce - // alphabet character. At that time, the character may be produced - // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT - // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without - // Shift key but with Shift key, it produces '%'. - // If the unshiftedLatinChar is produced by the key on current - // keyboard layout, we shouldn't append it to alternativeCharCode. - if (unshiftedLatinChar == unshiftedChars.mChars[0] || - unshiftedLatinChar == shiftedChars.mChars[0]) { - unshiftedLatinChar = 0; - } - } - - // If the charCode is not ASCII character, we should replace the - // charCode with ASCII character only when Ctrl is pressed. - // But don't replace the charCode when the charCode is not same as - // unmodified characters. In such case, Ctrl is sometimes used for a - // part of character inputting key combination like Shift. - if (aModKeyState.IsControl()) { - uint32_t ch = - aModKeyState.IsShift() ? shiftedLatinChar : unshiftedLatinChar; - if (ch && - (!inputtingChars.mLength || - inputtingChars.UniCharsCaseInsensitiveEqual( - aModKeyState.IsShift() ? shiftedChars : unshiftedChars))) { - inputtingChars.Clear(); - inputtingChars.Append(ch, aModKeyState.GetModifiers()); - } - } - } - - if (inputtingChars.mLength || - shiftedChars.mLength || unshiftedChars.mLength) { - uint32_t num = std::max(inputtingChars.mLength, - std::max(shiftedChars.mLength, unshiftedChars.mLength)); - uint32_t skipUniChars = num - inputtingChars.mLength; - uint32_t skipShiftedChars = num - shiftedChars.mLength; - uint32_t skipUnshiftedChars = num - unshiftedChars.mLength; - UINT keyCode = !inputtingChars.mLength ? DOMKeyCode : 0; - for (uint32_t cnt = 0; cnt < num; cnt++) { - uint16_t uniChar, shiftedChar, unshiftedChar; - uniChar = shiftedChar = unshiftedChar = 0; - ModifierKeyState modKeyState(aModKeyState); - if (skipUniChars <= cnt) { - if (cnt - skipUniChars < inputtingChars.mLength) { - // If key in combination with Alt and/or Ctrl produces a different - // character than without them then do not report these flags - // because it is separate keyboard layout shift state. If dead-key - // and base character does not produce a valid composite character - // then both produced dead-key character and following base - // character may have different modifier flags, too. - modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | - MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); - modKeyState.Set(inputtingChars.mModifiers[cnt - skipUniChars]); - } - uniChar = inputtingChars.mChars[cnt - skipUniChars]; - } - if (skipShiftedChars <= cnt) - shiftedChar = shiftedChars.mChars[cnt - skipShiftedChars]; - if (skipUnshiftedChars <= cnt) - unshiftedChar = unshiftedChars.mChars[cnt - skipUnshiftedChars]; - nsAutoTArray altArray; - - if (shiftedChar || unshiftedChar) { - nsAlternativeCharCode chars(unshiftedChar, shiftedChar); - altArray.AppendElement(chars); - } - if (cnt == num - 1) { - if (unshiftedLatinChar || shiftedLatinChar) { - nsAlternativeCharCode chars(unshiftedLatinChar, shiftedLatinChar); - altArray.AppendElement(chars); - } - - // Typically, following virtual keycodes are used for a key which can - // input the character. However, these keycodes are also used for - // other keys on some keyboard layout. E.g., in spite of Shift+'1' - // inputs '+' on Thai keyboard layout, a key which is at '=/+' - // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications - // handle it as '+' key if Ctrl key is pressed. - PRUnichar charForOEMKeyCode = 0; - switch (virtualKeyCode) { - case VK_OEM_PLUS: charForOEMKeyCode = '+'; break; - case VK_OEM_COMMA: charForOEMKeyCode = ','; break; - case VK_OEM_MINUS: charForOEMKeyCode = '-'; break; - case VK_OEM_PERIOD: charForOEMKeyCode = '.'; break; - } - if (charForOEMKeyCode && - charForOEMKeyCode != unshiftedChars.mChars[0] && - charForOEMKeyCode != shiftedChars.mChars[0] && - charForOEMKeyCode != unshiftedLatinChar && - charForOEMKeyCode != shiftedLatinChar) { - nsAlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode); - altArray.AppendElement(OEMChars); - } - } - - nsKeyEvent keypressEvent(true, NS_KEY_PRESS, this); - keypressEvent.mFlags.Union(extraFlags); - keypressEvent.charCode = uniChar; - keypressEvent.alternativeCharCodes.AppendElements(altArray); - InitKeyEvent(keypressEvent, nativeKey, modKeyState); - DispatchKeyEvent(keypressEvent, nullptr); - } - } else { - nsKeyEvent keypressEvent(true, NS_KEY_PRESS, this); - keypressEvent.mFlags.Union(extraFlags); - keypressEvent.keyCode = DOMKeyCode; - InitKeyEvent(keypressEvent, nativeKey, aModKeyState); - DispatchKeyEvent(keypressEvent, nullptr); - } - - return noDefault; -} - -// OnKeyUp -LRESULT nsWindow::OnKeyUp(const MSG &aMsg, - const ModifierKeyState &aModKeyState, - bool *aEventDispatched) -{ - // NOTE: VK_PROCESSKEY never comes with WM_KEYUP - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, - ("nsWindow::OnKeyUp wParam(VK)=%d\n", aMsg.wParam)); - - if (aEventDispatched) - *aEventDispatched = true; - nsKeyEvent keyupEvent(true, NS_KEY_UP, this); - NativeKey nativeKey(gKbdLayout, this, aMsg); - gKbdLayout.InitNativeKey(nativeKey, aModKeyState); - keyupEvent.keyCode = nativeKey.GetDOMKeyCode(); - InitKeyEvent(keyupEvent, nativeKey, aModKeyState); - // Set defaultPrevented of the key event if the VK_MENU is not a system key - // release, so that the menu bar does not trigger. This helps avoid - // triggering the menu bar for ALT key accelerators used in assistive - // technologies such as Window-Eyes and ZoomText or for switching open state - // of IME. - keyupEvent.mFlags.mDefaultPrevented = - (aMsg.wParam == VK_MENU && aMsg.message != WM_SYSKEYUP); - return DispatchKeyEvent(keyupEvent, &aMsg); -} - -// OnChar -LRESULT nsWindow::OnChar(const MSG &aMsg, - const NativeKey& aNativeKey, - const ModifierKeyState &aModKeyState, - bool *aEventDispatched, - const EventFlags *aExtraFlags) -{ - // ignore [shift+]alt+space so the OS can handle it - if (aModKeyState.IsAlt() && !aModKeyState.IsControl() && - IS_VK_DOWN(NS_VK_SPACE)) { - return FALSE; - } - - uint32_t charCode = aMsg.wParam; - // Ignore Ctrl+Enter (bug 318235) - if (aModKeyState.IsControl() && charCode == 0xA) { - return FALSE; - } - - // WM_CHAR with Control and Alt (== AltGr) down really means a normal character - ModifierKeyState modKeyState(aModKeyState); - if (modKeyState.IsAlt() && modKeyState.IsControl()) { - modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); - } - - if (IMEHandler::IsComposingOn(this)) { - IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION); - } - - wchar_t uniChar; - // Ctrl+A Ctrl+Z, see Programming Windows 3.1 page 110 for details - if (modKeyState.IsControl() && charCode <= 0x1A) { - // need to account for shift here. bug 16486 - if (modKeyState.IsShift()) { - uniChar = charCode - 1 + 'A'; - } else { - uniChar = charCode - 1 + 'a'; - } - } else if (modKeyState.IsControl() && charCode <= 0x1F) { - // Fix for 50255 - <[> and <]> are not being processed. - // also fixes ctrl+\ (x1c), ctrl+^ (x1e) and ctrl+_ (x1f) - // for some reason the keypress handler need to have the uniChar code set - // with the addition of a upper case A not the lower case. - uniChar = charCode - 1 + 'A'; - } else { // 0x20 - SPACE, 0x3D - EQUALS - if (charCode < 0x20 || (charCode == 0x3D && modKeyState.IsControl())) { - uniChar = 0; - } else { - uniChar = charCode; - } - } - - // Keep the characters unshifted for shortcuts and accesskeys and make sure - // that numbers are always passed as such (among others: bugs 50255 and 351310) - if (uniChar && (modKeyState.IsControl() || modKeyState.IsAlt())) { - UINT virtualKeyCode = ::MapVirtualKeyEx(aNativeKey.GetScanCode(), - MAPVK_VSC_TO_VK, - gKbdLayout.GetLayout()); - UINT unshiftedCharCode = - virtualKeyCode >= '0' && virtualKeyCode <= '9' ? virtualKeyCode : - modKeyState.IsShift() ? ::MapVirtualKeyEx(virtualKeyCode, - MAPVK_VK_TO_CHAR, - gKbdLayout.GetLayout()) : 0; - // ignore diacritics (top bit set) and key mapping errors (char code 0) - if ((INT)unshiftedCharCode > 0) - uniChar = unshiftedCharCode; - } - - // Fix for bug 285161 (and 295095) which was caused by the initial fix for bug 178110. - // When pressing (alt|ctrl)+char, the char must be lowercase unless shift is - // pressed too. - if (!modKeyState.IsShift() && - (aModKeyState.IsAlt() || aModKeyState.IsControl())) { - uniChar = towlower(uniChar); - } - - nsKeyEvent keypressEvent(true, NS_KEY_PRESS, this); - if (aExtraFlags) { - keypressEvent.mFlags.Union(*aExtraFlags); - } - keypressEvent.charCode = uniChar; - if (!keypressEvent.charCode) { - keypressEvent.keyCode = aNativeKey.GetDOMKeyCode(); - } - InitKeyEvent(keypressEvent, aNativeKey, modKeyState); - bool result = DispatchKeyEvent(keypressEvent, &aMsg); - if (aEventDispatched) - *aEventDispatched = true; - return result; -} - -void -nsWindow::SetupKeyModifiersSequence(nsTArray* aArray, uint32_t aModifiers) -{ - for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) { - const uint32_t* map = sModifierKeyMap[i]; - if (aModifiers & map[0]) { - aArray->AppendElement(KeyPair(map[1], map[2])); - } - } -} - static BOOL WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam) { *((HWND*)lParam) = hwnd; @@ -7393,7 +6578,7 @@ NS_IMETHODIMP_(InputContext) nsWindow::GetInputContext() { mInputContext.mIMEState.mOpen = IMEState::CLOSED; - if (IMEHandler::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) { + if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) { mInputContext.mIMEState.mOpen = IMEState::OPEN; } else { mInputContext.mIMEState.mOpen = IMEState::CLOSED; diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h index fe81e866c2a7..3502e3ce4647 100644 --- a/widget/windows/nsWindow.h +++ b/widget/windows/nsWindow.h @@ -195,11 +195,6 @@ public: int16_t aButton = nsMouseEvent::eLeftButton, uint16_t aInputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE); virtual bool DispatchWindowEvent(nsGUIEvent*event, nsEventStatus &aStatus); - void InitKeyEvent(nsKeyEvent& aKeyEvent, - const NativeKey& aNativeKey, - const mozilla::widget::ModifierKeyState &aModKeyState); - virtual bool DispatchKeyEvent(nsKeyEvent& aKeyEvent, - const MSG *aMsgSentToPlugin); void DispatchPendingEvents(); bool DispatchPluginEvent(UINT aMessage, WPARAM aWParam, @@ -228,11 +223,6 @@ public: */ virtual bool AutoErase(HDC dc); nsIntPoint* GetLastPoint() { return &mLastPoint; } - // needed in nsIMM32Handler.cpp - bool PluginHasFocus() - { - return (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN); - } bool IsTopLevelWidget() { return mIsTopWidgetWindow; } /** * Start allowing Direct3D9 to be used by widgets when GetLayerManager is @@ -279,8 +269,6 @@ public: bool const DestroyCalled() { return mDestroyCalled; } - static void SetupKeyModifiersSequence(nsTArray* aArray, uint32_t aModifiers); - virtual mozilla::layers::LayersBackend GetPreferredCompositorBackend() { return mozilla::layers::LAYERS_D3D11; } protected: @@ -331,15 +319,10 @@ protected: /** * Event processing helpers */ - bool DispatchPluginEvent(const MSG &aMsg); void DispatchFocusToTopLevelWindow(bool aIsActivate); bool DispatchStandardEvent(uint32_t aMsg); bool DispatchCommandEvent(uint32_t aEventCommand); void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam); - static void RemoveNextCharMessage(HWND aWnd); - void RemoveMessageAndDispatchPluginEvent(UINT aFirstMsg, - UINT aLastMsg, - nsFakeCharMessage* aFakeCharMessage = nullptr); virtual bool ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, LRESULT *aRetValue); bool ProcessMessageForPlugin(const MSG &aMsg, @@ -355,11 +338,6 @@ protected: static bool ConvertStatus(nsEventStatus aStatus); static void PostSleepWakeNotification(const bool aIsSleepMode); int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my); - static bool IsRedirectedKeyDownMessage(const MSG &aMsg); - static void ForgetRedirectedKeyDownMessage() - { - sRedirectedKeyDown.message = WM_NULL; - } /** * Event handlers @@ -367,28 +345,9 @@ protected: virtual void OnDestroy(); virtual bool OnMove(int32_t aX, int32_t aY); virtual bool OnResize(nsIntRect &aWindowRect); - /** - * @param aVirtualKeyCode If caller knows which key exactly caused the - * aMsg, set the virtual key code. - * Otherwise, 0. - * @param aScanCode If aVirutalKeyCode isn't 0, set the scan code. - */ - LRESULT OnChar(const MSG &aMsg, - const NativeKey& aNativeKey, - const mozilla::widget::ModifierKeyState &aModKeyState, - bool *aEventDispatched, - const mozilla::widget::EventFlags* aExtraFlags = nullptr); - LRESULT OnKeyDown(const MSG &aMsg, - const mozilla::widget::ModifierKeyState &aModKeyState, - bool *aEventDispatched, - nsFakeCharMessage* aFakeCharMessage); - LRESULT OnKeyUp(const MSG &aMsg, - const mozilla::widget::ModifierKeyState &aModKeyState, - bool *aEventDispatched); bool OnGesture(WPARAM wParam, LPARAM lParam); bool OnTouch(WPARAM wParam, LPARAM lParam); bool OnHotKey(WPARAM wParam, LPARAM lParam); - BOOL OnInputLangChange(HKL aHKL); bool OnPaint(HDC aDC, uint32_t aNestingLevel); void OnWindowPosChanged(WINDOWPOS *wp, bool& aResult); void OnWindowPosChanging(LPWINDOWPOS& info); @@ -477,7 +436,6 @@ protected: uint32_t mBlurSuppressLevel; DWORD_PTR mOldStyle; DWORD_PTR mOldExStyle; - InputContext mInputContext; nsNativeDragTarget* mNativeDragTarget; HKL mLastKeyboardLayout; nsSizeMode mOldSizeMode; @@ -573,46 +531,8 @@ protected: // painting too rapidly in response to frequent input events. TimeStamp mLastPaintEndTime; - // sRedirectedKeyDown is WM_KEYDOWN message or WM_SYSKEYDOWN message which - // was reirected to SendInput() API by OnKeyDown(). - static MSG sRedirectedKeyDown; - static bool sNeedsToInitMouseWheelSettings; static void InitMouseWheelScrollData(); - - // If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is - // redirected message, OnKeyDowm() prevents to dispatch NS_KEY_DOWN event - // because it has been dispatched before the message was redirected. - // However, in some cases, ProcessKeyDownMessage() doesn't call OnKeyDown(). - // Then, ProcessKeyDownMessage() needs to forget the redirected message and - // remove WM_CHAR message or WM_SYSCHAR message for the redirected keydown - // message. AutoForgetRedirectedKeyDownMessage struct is a helper struct - // for doing that. This must be created in stack. - struct AutoForgetRedirectedKeyDownMessage - { - AutoForgetRedirectedKeyDownMessage(nsWindow* aWindow, const MSG &aMsg) : - mCancel(!nsWindow::IsRedirectedKeyDownMessage(aMsg)), - mWindow(aWindow), mMsg(aMsg) - { - } - - ~AutoForgetRedirectedKeyDownMessage() - { - if (mCancel) { - return; - } - // Prevent unnecessary keypress event - if (!mWindow->mOnDestroyCalled) { - nsWindow::RemoveNextCharMessage(mWindow->mWnd); - } - // Foreget the redirected message - nsWindow::ForgetRedirectedKeyDownMessage(); - } - - bool mCancel; - nsRefPtr mWindow; - const MSG &mMsg; - }; }; /** diff --git a/widget/windows/nsWindowBase.h b/widget/windows/nsWindowBase.h index 38643b63e3a2..856baf1f7010 100644 --- a/widget/windows/nsWindowBase.h +++ b/widget/windows/nsWindowBase.h @@ -7,6 +7,9 @@ #define nsWindowBase_h_ #include "nsBaseWidget.h" +#include "nsGUIEvent.h" +#include "npapi.h" +#include /* * nsWindowBase - Base class of common methods other classes need to access @@ -32,6 +35,37 @@ public: * Dispatch a gecko event for this widget. */ virtual bool DispatchWindowEvent(nsGUIEvent* aEvent) = 0; + + /* + * Dispatch a plugin event with the message. + */ + virtual bool DispatchPluginEvent(const MSG &aMsg) MOZ_FINAL + { + if (!PluginHasFocus()) { + return false; + } + nsPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, this); + nsIntPoint point(0, 0); + InitEvent(pluginEvent, &point); + NPEvent npEvent; + npEvent.event = aMsg.message; + npEvent.wParam = aMsg.wParam; + npEvent.lParam = aMsg.lParam; + pluginEvent.pluginEvent = (void *)&npEvent; + pluginEvent.retargetToFocusedDocument = true; + return DispatchWindowEvent(&pluginEvent); + } + + /* + * Returns true if a plugin has focus on this widget. Otherwise, false. + */ + virtual bool PluginHasFocus() const MOZ_FINAL + { + return (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN); + } + +protected: + InputContext mInputContext; }; #endif // nsWindowBase_h_ diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h index ae22b195ac07..56200d9572a1 100644 --- a/widget/windows/nsWindowDefs.h +++ b/widget/windows/nsWindowDefs.h @@ -197,43 +197,12 @@ const char kClassNameDialog[] = "MozillaDialogClass"; const char kClassNameDropShadow[] = "MozillaDropShadowWindowClass"; const char kClassNameTemp[] = "MozillaTempWindowClass"; -static const uint32_t sModifierKeyMap[][3] = { - { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 }, - { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 }, - { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT }, - { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT }, - { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL }, - { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL }, - { nsIWidget::ALT_L, VK_MENU, VK_LMENU }, - { nsIWidget::ALT_R, VK_MENU, VK_RMENU } -}; - /************************************************************** * * SECTION: structs * **************************************************************/ -// Used in OnKeyDown -struct nsAlternativeCharCode; // defined in nsGUIEvent.h -struct nsFakeCharMessage { - UINT mCharCode; - UINT mScanCode; - bool mIsDeadKey; - - MSG GetCharMessage(HWND aWnd) - { - MSG msg; - msg.hwnd = aWnd; - msg.message = mIsDeadKey ? WM_DEADCHAR : WM_CHAR; - msg.wParam = static_cast(mCharCode); - msg.lParam = static_cast(mScanCode); - msg.time = 0; - msg.pt.x = msg.pt.y = 0; - return msg; - } -}; - // Used for synthesizing events struct KeyPair { uint8_t mGeneral; diff --git a/widget/windows/winrt/MetroWidget.h b/widget/windows/winrt/MetroWidget.h index 5e40d1cf4d61..fa9d6956be89 100644 --- a/widget/windows/winrt/MetroWidget.h +++ b/widget/windows/winrt/MetroWidget.h @@ -204,6 +204,5 @@ protected: nsCOMPtr mIdleService; HWND mWnd; WNDPROC mMetroWndProc; - nsIWidget::InputContext mInputContext; bool mTempBasicLayerInUse; }; diff --git a/xpcom/base/nsConsoleMessage.cpp b/xpcom/base/nsConsoleMessage.cpp index 119e1b1d942c..6c5c005c8db4 100644 --- a/xpcom/base/nsConsoleMessage.cpp +++ b/xpcom/base/nsConsoleMessage.cpp @@ -9,29 +9,41 @@ #include "nsConsoleMessage.h" #include "nsReadableUtils.h" +#include "jsapi.h" NS_IMPL_THREADSAFE_ISUPPORTS1(nsConsoleMessage, nsIConsoleMessage) -nsConsoleMessage::nsConsoleMessage() +nsConsoleMessage::nsConsoleMessage() + : mMessage(), + mTimeStamp(0) { } -nsConsoleMessage::nsConsoleMessage(const PRUnichar *message) +nsConsoleMessage::nsConsoleMessage(const PRUnichar *message) { - mMessage.Assign(message); + mTimeStamp = JS_Now() / 1000; + mMessage.Assign(message); } NS_IMETHODIMP -nsConsoleMessage::GetMessageMoz(PRUnichar **result) { - *result = ToNewUnicode(mMessage); +nsConsoleMessage::GetMessageMoz(PRUnichar **result) +{ + *result = ToNewUnicode(mMessage); - return NS_OK; + return NS_OK; } -// NS_IMETHODIMP -// nsConsoleMessage::Init(const PRUnichar *message) { -// nsAutoString newMessage(message); -// mMessage = ToNewUnicode(newMessage); -// return NS_OK; -// } +NS_IMETHODIMP +nsConsoleMessage::GetTimeStamp(int64_t *aTimeStamp) +{ + *aTimeStamp = mTimeStamp; + return NS_OK; +} +NS_IMETHODIMP +nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) +{ + CopyUTF16toUTF8(mMessage, aResult); + + return NS_OK; +} diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h index 6fae2655b0e2..95fbd3864def 100644 --- a/xpcom/base/nsConsoleMessage.h +++ b/xpcom/base/nsConsoleMessage.h @@ -22,6 +22,7 @@ public: private: ~nsConsoleMessage() {} + int64_t mTimeStamp; nsString mMessage; }; diff --git a/xpcom/base/nsIConsoleMessage.idl b/xpcom/base/nsIConsoleMessage.idl index 360e5360ac06..ba6a80f8d93e 100644 --- a/xpcom/base/nsIConsoleMessage.idl +++ b/xpcom/base/nsIConsoleMessage.idl @@ -10,13 +10,23 @@ * provide an object that can be qi'ed to provide more specific * message information. */ -[scriptable, uuid(41bd8784-1dd2-11b2-9553-8606958fffe1)] +[scriptable, uuid(c14c151b-5ea4-47ed-8e85-d392cdd3e154)] interface nsIConsoleMessage : nsISupports { + /** + * The time (in milliseconds from the Epoch) that the message instance + * was initialised. + * The timestamp is initialized as JS_now/1000 so that it can be + * compared to Date.now in Javascript. + */ + readonly attribute long long timeStamp; + [binaryname(MessageMoz)] readonly attribute wstring message; + + AUTF8String toString(); }; %{ C++ #define NS_CONSOLEMESSAGE_CID \ -{ 0x56c9d666, 0x1dd2, 0x11b2, { 0xb4, 0x3c, 0xa8, 0x4b, 0xf3, 0xb3, 0xec, 0xbb }} +{ 0x024efc9e, 0x54dc, 0x4844, { 0x80, 0x4b, 0x41, 0xd3, 0xf3, 0x69, 0x90, 0x73 }} %} diff --git a/xpcom/build/nsXPComInit.cpp b/xpcom/build/nsXPComInit.cpp index 5882f7b4a8fb..a4e378540b13 100644 --- a/xpcom/build/nsXPComInit.cpp +++ b/xpcom/build/nsXPComInit.cpp @@ -332,7 +332,8 @@ NS_InitXPCOM2(nsIServiceManager* *result, { mozPoisonValueInit(); - profiler_init(); + char aLocal; + profiler_init(&aLocal); nsresult rv = NS_OK; // We are not shutting down diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp index eff6cfbb1519..1ac0cacd42ab 100644 --- a/xpcom/threads/LazyIdleThread.cpp +++ b/xpcom/threads/LazyIdleThread.cpp @@ -169,7 +169,8 @@ LazyIdleThread::EnsureThread() void LazyIdleThread::InitThread() { - profiler_register_thread(mName.get()); + char aLocal; + profiler_register_thread(mName.get(), &aLocal); PR_SetCurrentThreadName(mName.get());