Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Wed, 22 Aug 2018 12:54:34 +0300
changeset 481105 e5ac55ad643d95424b8891446a3743b53bcf6bfe
parent 481079 ab61273bea17b941baca4f3f85b6ca0362d56e22 (current diff)
parent 481104 3b5452b3778153f6f223bb2177235835b2314eb6 (diff)
child 481106 fb399f543ea6a888ac6d3f2ccec37bb067b9d601
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersmerge
milestone63.0a1
Merge mozilla-central to inbound. a=merge CLOSED TREE
layout/style/ServoCSSPropList.mako.py
layout/style/nsComputedDOMStyle.cpp
servo/components/style/gecko/conversions.rs
servo/components/style/properties/longhands/box.mako.rs
--- a/browser/components/payments/test/browser/browser_show_dialog.js
+++ b/browser/components/payments/test/browser/browser_show_dialog.js
@@ -157,8 +157,48 @@ add_task(async function test_show_closeR
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
 
     let result = await ContentTask.spawn(browser, null, async () => content.rqResult);
     ok(result.showException, "Expected promise rejection from the rq.show() promise");
     ok(!result.response,
        "rq.show() shouldn't resolve to a response");
   });
 });
+
+add_task(async function test_localized() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    let {win, frame} =
+      await setupPaymentDialog(browser, {
+        methodData,
+        details,
+        merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+      }
+    );
+
+    await spawnPaymentDialogTask(frame, async function check_l10n() {
+      await ContentTaskUtils.waitForCondition(() => {
+        let telephoneLabel = content.document.querySelector("#tel-container > .label-text");
+        return telephoneLabel && telephoneLabel.textContent.includes("Phone");
+      }, "Check that the telephone number label is localized");
+
+      await ContentTaskUtils.waitForCondition(() => {
+        let ccNumberField = content.document.querySelector("#cc-number");
+        if (!ccNumberField) {
+          return false;
+        }
+        let ccNumberLabel = ccNumberField.parentElement.querySelector(".label-text");
+        return ccNumberLabel.textContent.includes("Number");
+      }, "Check that the cc-number label is localized");
+
+      const L10N_ATTRIBUTE_SELECTOR = "[data-localization], [data-localization-region]";
+      await ContentTaskUtils.waitForCondition(() => {
+        return content.document.querySelectorAll(L10N_ATTRIBUTE_SELECTOR).length === 0;
+      }, "Check that there are no unlocalized strings");
+    });
+
+    // abort the payment request
+    ContentTask.spawn(browser, null, async () => content.rq.abort());
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+});
--- a/browser/extensions/formautofill/content/l10n.js
+++ b/browser/extensions/formautofill/content/l10n.js
@@ -20,22 +20,38 @@ const L10N_ATTRIBUTES = ["data-localizat
 
 // eslint-disable-next-line mozilla/balanced-listeners
 CONTENT_WIN.addEventListener("DOMContentLoaded", function onDCL(evt) {
   let doc = evt.target;
   FormAutofillUtils.localizeMarkup(doc);
 
   let mutationObserver = new doc.ownerGlobal.MutationObserver(function onMutation(mutations) {
     for (let mutation of mutations) {
-      if (!mutation.target.hasAttribute(mutation.attributeName)) {
-        // The attribute was removed in the meantime.
-        continue;
+      switch (mutation.type) {
+        case "attributes": {
+          if (!mutation.target.hasAttribute(mutation.attributeName)) {
+            // The attribute was removed in the meantime.
+            continue;
+          }
+          FormAutofillUtils.localizeAttributeForElement(mutation.target, mutation.attributeName);
+          break;
+        }
+
+        case "childList": {
+          // We really only care about the <form>s appending inside pages.
+          if (!mutation.addedNodes || !mutation.target.classList ||
+              !mutation.target.classList.contains("page")) {
+            break;
+          }
+          FormAutofillUtils.localizeMarkup(mutation.target);
+          break;
+        }
       }
-      FormAutofillUtils.localizeAttributeForElement(mutation.target, mutation.attributeName);
     }
   });
 
   mutationObserver.observe(doc, {
     attributes: true,
     attributeFilter: L10N_ATTRIBUTES,
+    childList: true,
     subtree: true,
   });
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/README.md
@@ -0,0 +1,123 @@
+# about:debugging-new architecture document
+
+## document purpose
+The purpose of this document is to explain how the new about:debugging UI is built using React and Redux. It should serve both as a documentation resource and as a technical specification. Changes impacting the architecture should preferably be discussed and reflected here before implementation.
+
+## high level architecture
+about:debugging-new is an "about" page registered via a component manifest that is located in `/devtools/startup/aboutdebugging.manifest`. The component registration code is at `/devtools/startup/aboutdebugging-registration.js` and mostly contains the logic to switch between the old and the new about:debugging UI, based on the value of the preference `devtools.aboutdebugging.new-enabled`.
+
+The about:debugging-new UI is built using React and Redux. The various React/Redux files should be organized as follows:
+- devtools/client/aboutdebugging-new/src/actions
+- devtools/client/aboutdebugging-new/src/components (js and css)
+- devtools/client/aboutdebugging-new/src/middleware
+- devtools/client/aboutdebugging-new/src/reducers
+
+### actions
+Actions should cover all user or external events that change the UI.
+
+#### external actions
+
+##### to be implemented
+  - Runtime information updated
+  - List of devices updated
+  - List of addons/tabs/workers(...) updated
+  - Connection status of device changes
+  - ADBHelper is enabled/disabled/in-progress
+  - WiFi debugging is enabled/disabled/in-progress
+  - Network location list is updated
+
+#### user actions
+
+##### already implemented
+  - sidebar
+   - actions/ui:selectPage returns PAGE_SELECTED action
+
+##### to be implemented
+  - sidebar
+    - Select device (extended version of selectPage)
+    - Connect to device
+    - Cancel connection to device
+  - device page
+    - Disconnect from device
+    - Take screenshot
+    - Toggle collapsible section
+    - Add tab on device
+    - Inspect a debugging target
+  - device page - addon controls
+    - enable addon debugging
+    - Load temporary addon
+    - Reload temporary addon
+    - Remove temporary addon
+  - device page - worker controls
+    - Start worker
+    - Debug worker
+    - Push worker
+    - Unregister worker
+  - connect page
+    - Add network location
+    - Remove network location
+
+#### asynchronous actions
+For asynchronous actions, we will use the thunk middleware, similar to what it done in the webconsole and netmonitor. An asynchronous action should be split in three actions:
+- start
+- success
+- failure
+
+As we will implement asynchronous, we should aim to keep a consistent naming for those actions.
+
+A typical usecase for an asynchronous action here would be selecting a runtime. The selection will be immediate but should trigger actions to retrieve tabs, addons, workers etc… which will all be asynchronous.
+
+### components
+Components should avoid dealing with specialized objects as much as possible.
+
+They should never use any of the lifecycle methods that will be deprecated in React 17 (componentWillMount, componentWillReceiveProps, and componentWillUpdate).
+
+When it comes to updating the state, components should do it via an action if the result of this action is something that should be preserved when reloading the UI.
+
+### state and reducers
+The state represents the model for the UI of the application.
+
+#### state
+This is a representation of our current state.
+
+  ui:
+    - selectedPage: String
+
+##### to be implemented
+Some ideas of how we could extend the state. Names, properties, values are all up for discussion.
+
+  runtimes: Array of runtimes (only currently detected runtimes)
+  targets: (currently visible targets, only relevant on a device page)
+    - addons: Array of target
+    - processes: Array of target
+    - tabs: Array of target
+    - workers: Array of target
+  config:
+    - adbhelper: boolean
+    - wifi: boolean
+    - network-locations: Array of network-location
+
+#### types
+Other panels are sometimes relying on a types.js file committed under src, providing types that can be shared and reused in various places of the application.
+
+Example of types.js file:
+  https://searchfox.org/mozilla-central/source/devtools/client/inspector/fonts/types.js
+
+We might follow that kind of approach when we need to reuse similar objects in several components.
+
+Examples of types that we could introduce here:
+  runtime:
+    - type
+    - name
+    - icon
+    - version
+
+  target:
+    - type
+    - name
+    - icon
+    - details
+
+  network-location:
+    - host: String
+    - port: String
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -151,16 +151,50 @@ class JSTerm extends Component {
     const toolbox = gDevTools.getToolbox(this.hud.owner.target);
     const tooltipDoc = toolbox ? toolbox.doc : doc;
     // The popup will be attached to the toolbox document or HUD document in the case
     // such as the browser console which doesn't have a toolbox.
     this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
 
     if (this.props.codeMirrorEnabled) {
       if (this.node) {
+        const onArrowUp = () => {
+          let inputUpdated;
+          if (this.autocompletePopup.isOpen) {
+            this.autocompletePopup.selectPreviousItem();
+            return null;
+          }
+
+          if (this.canCaretGoPrevious()) {
+            inputUpdated = this.historyPeruse(HISTORY_BACK);
+          }
+
+          if (!inputUpdated) {
+            return "CodeMirror.Pass";
+          }
+          return null;
+        };
+
+        const onArrowDown = () => {
+          let inputUpdated;
+          if (this.autocompletePopup.isOpen) {
+            this.autocompletePopup.selectNextItem();
+            return null;
+          }
+
+          if (this.canCaretGoNext()) {
+            inputUpdated = this.historyPeruse(HISTORY_FORWARD);
+          }
+
+          if (!inputUpdated) {
+            return "CodeMirror.Pass";
+          }
+          return null;
+        };
+
         this.editor = new Editor({
           autofocus: true,
           enableCodeFolding: false,
           gutters: [],
           lineWrapping: true,
           mode: Editor.modes.js,
           styleActiveLine: false,
           tabIndex: "0",
@@ -201,49 +235,21 @@ class JSTerm extends Component {
                 this.insertStringAtCursor("\t");
                 return false;
               }
 
               // Something is selected, let the editor handle the indent.
               return true;
             },
 
-            "Up": () => {
-              let inputUpdated;
-              if (this.autocompletePopup.isOpen) {
-                this.autocompletePopup.selectPreviousItem();
-                return null;
-              }
-
-              if (this.canCaretGoPrevious()) {
-                inputUpdated = this.historyPeruse(HISTORY_BACK);
-              }
-
-              if (!inputUpdated) {
-                return "CodeMirror.Pass";
-              }
-              return null;
-            },
+            "Up": onArrowUp,
+            "Cmd-Up": onArrowUp,
 
-            "Down": () => {
-              let inputUpdated;
-              if (this.autocompletePopup.isOpen) {
-                this.autocompletePopup.selectNextItem();
-                return null;
-              }
-
-              if (this.canCaretGoNext()) {
-                inputUpdated = this.historyPeruse(HISTORY_FORWARD);
-              }
-
-              if (!inputUpdated) {
-                return "CodeMirror.Pass";
-              }
-              return null;
-            },
+            "Down": onArrowDown,
+            "Cmd-Down": onArrowDown,
 
             "Left": () => {
               if (this.autocompletePopup.isOpen || this.getAutoCompletionText()) {
                 this.clearCompletion();
               }
               return "CodeMirror.Pass";
             },
 
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_history_arrow_keys.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_history_arrow_keys.js
@@ -99,16 +99,50 @@ async function performTests() {
   checkInput("document;\nwindow;\ndocument.body|", "↓ again: input #3 is correct");
   ok(inputHasNoSelection(jsterm));
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
   checkInput("document.location|", "↓: input #4 is correct");
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
   checkInput("|", "↓: input is empty");
+
+  info("Test that Cmd + ArrowDown/Up works as expected on OSX");
+  if (Services.appinfo.OS === "Darwin") {
+    const option = {metaKey: true};
+    EventUtils.synthesizeKey("KEY_ArrowUp", option);
+    checkInput("document.location|", "Cmd+↑ : input is correct");
+
+    EventUtils.synthesizeKey("KEY_ArrowUp", option);
+    checkInput("document;\nwindow;\ndocument.body|", "Cmd+↑ : input is correct");
+
+    EventUtils.synthesizeKey("KEY_ArrowUp", option);
+    checkInput("|document;\nwindow;\ndocument.body",
+      "Cmd+↑ : cursor is moved to the beginning of the input");
+
+    EventUtils.synthesizeKey("KEY_ArrowUp", option);
+    checkInput("document.body|", "Cmd+↑: input is correct");
+
+    EventUtils.synthesizeKey("KEY_ArrowDown", option);
+    checkInput("document;\nwindow;\ndocument.body|", "Cmd+↓ : input is correct");
+
+    EventUtils.synthesizeKey("KEY_ArrowUp", option);
+    checkInput("|document;\nwindow;\ndocument.body",
+      "Cmd+↑ : cursor is moved to the beginning of the input");
+
+    EventUtils.synthesizeKey("KEY_ArrowDown", option);
+    checkInput("document;\nwindow;\ndocument.body|",
+      "Cmd+↓ : cursor is moved to the end of the input");
+
+    EventUtils.synthesizeKey("KEY_ArrowDown", option);
+    checkInput("document.location|", "Cmd+↓ : input is correct");
+
+    EventUtils.synthesizeKey("KEY_ArrowDown", option);
+    checkInput("|", "Cmd+↓: input is empty");
+  }
 }
 
 function setCursorAtPosition(jsterm, pos) {
   const {inputNode, editor} = jsterm;
 
   if (editor) {
     let line = 0;
     let ch = 0;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2556,33 +2556,33 @@ HTMLEditor::Align(const nsAString& aAlig
     return rv;
   }
 
   return rules->DidDoAction(selection, subActionInfo, rv);
 }
 
 Element*
 HTMLEditor::GetElementOrParentByTagName(const nsAtom& aTagName,
-                                        nsINode* aNode)
+                                        nsINode* aNode) const
 {
   MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
 
   if (aNode) {
     return GetElementOrParentByTagNameInternal(aTagName, *aNode);
   }
   RefPtr<Selection> selection = GetSelection();
   if (NS_WARN_IF(!selection)) {
     return nullptr;
   }
   return GetElementOrParentByTagNameAtSelection(*selection, aTagName);
 }
 
 Element*
 HTMLEditor::GetElementOrParentByTagNameAtSelection(Selection& aSelection,
-                                                   const nsAtom& aTagName)
+                                                   const nsAtom& aTagName) const
 {
   MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
 
   // If no node supplied, get it from anchor node of current selection
   const EditorRawDOMPoint atAnchor(aSelection.AnchorRef());
   if (NS_WARN_IF(!atAnchor.IsSet())) {
     return nullptr;
   }
@@ -2600,17 +2600,17 @@ HTMLEditor::GetElementOrParentByTagNameA
       return nullptr;
     }
   }
   return GetElementOrParentByTagNameInternal(aTagName, *node);
 }
 
 Element*
 HTMLEditor::GetElementOrParentByTagNameInternal(const nsAtom& aTagName,
-                                                nsINode& aNode)
+                                                nsINode& aNode) const
 {
   MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
 
   Element* currentElement =
     aNode.IsElement() ? aNode.AsElement() : aNode.GetParentElement();
   if (NS_WARN_IF(!currentElement)) {
     // Neither aNode nor its parent is an element, so no ancestor is
     MOZ_ASSERT(!aNode.GetParentNode() ||
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -397,17 +397,17 @@ public:
    *                        has "name" attribute with non-empty value.
    * @param aNode           If non-nullptr, this starts to look for the result
    *                        from it.  Otherwise, i.e., nullptr, starts from
    *                        anchor node of Selection.
    * @return                If an element which matches aTagName, returns
    *                        an Element.  Otherwise, nullptr.
    */
   Element*
-  GetElementOrParentByTagName(const nsAtom& aTagName, nsINode* aNode);
+  GetElementOrParentByTagName(const nsAtom& aTagName, nsINode* aNode) const;
 
   /**
     * Get an active editor's editing host in DOM window.  If this editor isn't
     * active in the DOM window, this returns NULL.
     */
   Element* GetActiveEditingHost() const;
 
 protected: // May be called by friends.
@@ -907,17 +907,17 @@ protected: // Shouldn't be used by frien
    *                        which has "href" attribute with non-empty value.
    *                        If nsGkAtoms::anchor, the result may be <a> which
    *                        has "name" attribute with non-empty value.
    * @return                If an element which matches aTagName, returns
    *                        an Element.  Otherwise, nullptr.
    */
   Element*
   GetElementOrParentByTagNameAtSelection(Selection& aSelection,
-                                         const nsAtom& aTagName);
+                                         const nsAtom& aTagName) const;
 
   /**
    * GetElementOrParentByTagNameInternal() looks for an element node whose
    * name matches aTagName from aNode to <body> element.
    *
    * @param aTagName        The tag name which you want to look for.
    *                        Must not be nsGkAtoms::_empty.
    *                        If nsGkAtoms::list, the result may be <ul>, <ol> or
@@ -928,17 +928,17 @@ protected: // Shouldn't be used by frien
    *                        If nsGkAtoms::anchor, the result may be <a> which
    *                        has "name" attribute with non-empty value.
    * @param aNode           Start node to look for the element.
    * @return                If an element which matches aTagName, returns
    *                        an Element.  Otherwise, nullptr.
    */
   Element*
   GetElementOrParentByTagNameInternal(const nsAtom& aTagName,
-                                      nsINode& aNode);
+                                      nsINode& aNode) const;
 
   /**
    * GetSelectedElement() returns an element node which is in first range of
    * aSelection.  The rule is a little bit complicated and the rules do not
    * make sense except in a few cases.  If you want to use this newly,
    * you should create new method instead.  This needs to be here for
    * comm-central.
    * The rules are:
@@ -968,16 +968,50 @@ protected: // Shouldn't be used by frien
    * @return                    An element in first range of aSelection.
    */
   already_AddRefed<Element>
   GetSelectedElement(Selection& aSelection,
                      const nsAtom* aTagName,
                      ErrorResult& aRv);
 
   /**
+   * GetFirstTableRowElement() returns the first <tr> element in the most
+   * nearest ancestor of aTableOrElementInTable or itself.
+   *
+   * @param aTableOrElementInTable      <table> element or another element.
+   *                                    If this is a <table> element, returns
+   *                                    first <tr> element in it.  Otherwise,
+   *                                    returns first <tr> element in nearest
+   *                                    ancestor <table> element.
+   * @param aRv                         Returns an error code.  When
+   *                                    aTableOrElementInTable is neither
+   *                                    <table> nor in a <table> element,
+   *                                    returns NS_ERROR_FAILURE.
+   *                                    However, if <table> does not have
+   *                                    <tr> element, returns NS_OK.
+   */
+  Element*
+  GetFirstTableRowElement(Element& aTableOrElementInTable,
+                          ErrorResult& aRv) const;
+
+  /**
+   * GetNextTableRowElement() returns next <tr> element of aTableRowElement.
+   * This won't cross <table> element boundary but may cross table section
+   * elements like <tbody>.
+   *
+   * @param aTableRowElement    A <tr> element.
+   * @param aRv                 Returns error.  If given element is <tr> but
+   *                            there is no next <tr> element, this returns
+   *                            nullptr but does not return error.
+   */
+  Element*
+  GetNextTableRowElement(Element& aTableRowElement,
+                         ErrorResult& aRv) const;
+
+  /**
    * PasteInternal() pasts text with replacing selected content.
    * This tries to dispatch ePaste event first.  If its defaultPrevent() is
    * called, this does nothing but returns NS_OK.
    *
    * @param aClipboardType  nsIClipboard::kGlobalClipboard or
    *                        nsIClipboard::kSelectionClipboard.
    */
   nsresult PasteInternal(int32_t aClipboardType);
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -216,114 +216,133 @@ HTMLEditor::InsertTableCell(int32_t aNum
     }
   }
   // XXX This is perhaps the result of the last call of
   //     InsertNodeWithTransaction() or CreateElementWithDefaults().
   return rv;
 }
 
 NS_IMETHODIMP
-HTMLEditor::GetFirstRow(Element* aTableElement,
-                        nsINode** aRowNode)
+HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
+                        Element** aFirstRowElement)
 {
-  if (NS_WARN_IF(!aTableElement) || NS_WARN_IF(!aRowNode)) {
+  if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
     return NS_ERROR_INVALID_ARG;
   }
-
-  *aRowNode = nullptr;
+  ErrorResult error;
+  RefPtr<Element> firstRowElement =
+    GetFirstTableRowElement(*aTableOrElementInTable, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  firstRowElement.forget(aFirstRowElement);
+  return NS_OK;
+}
+
+Element*
+HTMLEditor::GetFirstTableRowElement(Element& aTableOrElementInTable,
+                                    ErrorResult& aRv) const
+{
+  MOZ_ASSERT(!aRv.Failed());
 
   Element* tableElement =
-    GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aTableElement);
+    GetElementOrParentByTagNameInternal(*nsGkAtoms::table,
+                                        aTableOrElementInTable);
+  // If the element is not in <table>, return error.
   if (NS_WARN_IF(!tableElement)) {
-    return NS_ERROR_FAILURE;
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
   }
 
-  nsCOMPtr<nsIContent> tableChild = tableElement->GetFirstChild();
-  while (tableChild) {
+  for (nsIContent* tableChild = tableElement->GetFirstChild();
+       tableChild;
+       tableChild = tableChild->GetNextSibling()) {
     if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
       // Found a row directly under <table>
-      tableChild.forget(aRowNode);
-      return NS_OK;
+      return tableChild->AsElement();
     }
-    // Look for row in one of the row container elements
+    // <table> can have table section elements like <tbody>.  <tr> elements
+    // may be children of them.
     if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody,
                                         nsGkAtoms::thead,
                                         nsGkAtoms::tfoot)) {
-      nsCOMPtr<nsIContent> rowNode = tableChild->GetFirstChild();
-
-      // We can encounter textnodes here -- must find a row
-      while (rowNode && !HTMLEditUtils::IsTableRow(rowNode)) {
-        rowNode = rowNode->GetNextSibling();
-      }
-
-      if (rowNode) {
-        rowNode.forget(aRowNode);
-        return NS_OK;
+      for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
+           tableSectionChild;
+           tableSectionChild = tableSectionChild->GetNextSibling()) {
+        if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
+          return tableSectionChild->AsElement();
+        }
       }
     }
-    // Here if table child was a CAPTION or COLGROUP
-    //  or child of a row parent wasn't a row (bad HTML?),
-    //  or first child was a textnode
-    // Look in next table child
-    tableChild = tableChild->GetNextSibling();
   }
-  // If here, row was not found
-  return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  // Don't return error when there is no <tr> element in the <table>.
+  return nullptr;
 }
 
-NS_IMETHODIMP
-HTMLEditor::GetNextRow(nsINode* aCurrentRowNode,
-                       nsINode** aRowNode)
+Element*
+HTMLEditor::GetNextTableRowElement(Element& aTableRowElement,
+                                   ErrorResult& aRv) const
 {
-  NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
-
-  *aRowNode = nullptr;
-
-  NS_ENSURE_TRUE(aCurrentRowNode, NS_ERROR_NULL_POINTER);
-
-  if (!HTMLEditUtils::IsTableRow(aCurrentRowNode)) {
-    return NS_ERROR_FAILURE;
+  MOZ_ASSERT(!aRv.Failed());
+
+  if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
   }
 
-  nsIContent* nextRow = aCurrentRowNode->GetNextSibling();
-
-  // Skip over any textnodes here
-  while (nextRow && !HTMLEditUtils::IsTableRow(nextRow)) {
-    nextRow = nextRow->GetNextSibling();
+  for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
+       maybeNextRow;
+       maybeNextRow = maybeNextRow->GetNextSibling()) {
+    if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
+      return maybeNextRow->AsElement();
+    }
+  }
+
+  // In current table section (e.g., <tbody>), there is no <tr> element.
+  // Then, check the following table sections.
+  Element* parentElementOfRow = aTableRowElement.GetParentElement();
+  if (NS_WARN_IF(!parentElementOfRow)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
   }
-  if (nextRow) {
-    *aRowNode = do_AddRef(nextRow).take();
-    return NS_OK;
+
+  // Basically, <tr> elements should be in table section elements even if
+  // they are not written in the source explicitly.  However, for preventing
+  // cross table boundary, check it now.
+  if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
+    // Don't return error since this means just not found.
+    return nullptr;
   }
 
-  // No row found, search for rows in other table sections
-  nsINode* rowParent = aCurrentRowNode->GetParentNode();
-  NS_ENSURE_TRUE(rowParent, NS_ERROR_NULL_POINTER);
-
-  nsIContent* parentSibling = rowParent->GetNextSibling();
-
-  while (parentSibling) {
-    nextRow = parentSibling->GetFirstChild();
-
-    // We can encounter textnodes here -- must find a row
-    while (nextRow && !HTMLEditUtils::IsTableRow(nextRow)) {
-      nextRow = nextRow->GetNextSibling();
+  for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
+       maybeNextTableSection;
+       maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
+    // If the sibling of parent of given <tr> is a table section element,
+    // check its children.
+    if (maybeNextTableSection->IsAnyOfHTMLElements(nsGkAtoms::tbody,
+                                                   nsGkAtoms::thead,
+                                                   nsGkAtoms::tfoot)) {
+      for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
+           maybeNextRow;
+           maybeNextRow = maybeNextRow->GetNextSibling()) {
+        if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
+          return maybeNextRow->AsElement();
+        }
+      }
     }
-    if (nextRow) {
-      *aRowNode = do_AddRef(nextRow).take();
-      return NS_OK;
+    // I'm not sure whether this is a possible case since table section
+    // elements are created automatically.  However, DOM API may create
+    // <tr> elements without table section elements.  So, let's check it.
+    else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
+      return maybeNextTableSection->AsElement();
     }
-
-    // We arrive here only if a table section has no children
-    //  or first child of section is not a row (bad HTML or more "_moz_text" nodes!)
-    // So look for another section sibling
-    parentSibling = parentSibling->GetNextSibling();
   }
-  // If here, row was not found
-  return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  // Don't return error when the given <tr> element is the last <tr> element in
+  // the <table>.
+  return nullptr;
 }
 
 nsresult
 HTMLEditor::GetLastCellInRow(nsINode* aRowNode,
                              nsINode** aCellNode)
 {
   NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER);
 
@@ -404,17 +423,18 @@ HTMLEditor::InsertTableColumn(int32_t aN
 
   // If we are inserting after all existing columns
   // Make sure table is "well formed"
   //  before appending new column
   if (startColIndex >= colCount) {
     NormalizeTable(table);
   }
 
-  nsCOMPtr<nsINode> rowNode;
+  ErrorResult error;
+  RefPtr<Element> rowElement;
   for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
     if (startColIndex < colCount) {
       // We are inserting before an existing column
       rv = GetCellDataAt(table, rowIndex, startColIndex,
                          getter_AddRefs(curCell),
                          &curStartRowIndex, &curStartColIndex,
                          &rowSpan, &colSpan,
                          &actualRowSpan, &actualColSpan, &isSelected);
@@ -437,34 +457,41 @@ HTMLEditor::InsertTableColumn(int32_t aN
           // Insert a new cell before current one
           selection->Collapse(curCell, 0);
           rv = InsertTableCell(aNumber, false);
         }
       }
     } else {
       // Get current row and append new cells after last cell in row
       if (!rowIndex) {
-        rv = GetFirstRow(table, getter_AddRefs(rowNode));
+        rowElement = GetFirstTableRowElement(*table, error);
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+      } else {
+        if (NS_WARN_IF(!rowElement)) {
+          // XXX Looks like that when rowIndex is 0, startColIndex is always
+          //     same as or larger than colCount.  Is it true?
+          return NS_ERROR_FAILURE;
+        }
+        rowElement = GetNextTableRowElement(*rowElement, error);
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+      }
+
+      if (rowElement) {
+        nsCOMPtr<nsINode> lastCell;
+        rv = GetLastCellInRow(rowElement, getter_AddRefs(lastCell));
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
-      } else {
-        nsCOMPtr<nsINode> nextRow;
-        rv = GetNextRow(rowNode, getter_AddRefs(nextRow));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
+        if (NS_WARN_IF(!lastCell)) {
+          return NS_ERROR_FAILURE;
         }
-        rowNode = nextRow;
-      }
-
-      if (rowNode) {
-        nsCOMPtr<nsINode> lastCell;
-        rv = GetLastCellInRow(rowNode, getter_AddRefs(lastCell));
-        NS_ENSURE_SUCCESS(rv, rv);
-        NS_ENSURE_TRUE(lastCell, NS_ERROR_FAILURE);
 
         curCell = lastCell->AsElement();
         // Simply add same number of cells to each row
         // Although tempted to check cell indexes for curCell,
         //  the effects of COLSPAN>1 in some cells makes this futile!
         // We must use NormalizeTable first to assure
         //  that there are cells in each cellmap location
         selection->Collapse(curCell, 0);
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -284,16 +284,17 @@ skip-if = android_version == '24'
 subsuite = clipboard
 skip-if = android_version == '24'
 [test_nsIHTMLEditor_getSelectedElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
 [test_nsIHTMLEditor_selectElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
 [test_nsIHTMLEditor_setCaretAfterElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
+[test_nsITableEditor_getFirstRow.html]
 [test_resizers_appearance.html]
 [test_resizers_resizing_elements.html]
 skip-if = android_version == '18' || (verify && debug && os == 'win') # bug 1147989
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_undo_after_spellchecker_replaces_word.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsITableEditor_getFirstRow.html
@@ -0,0 +1,105 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsITableEditor.getFirstRow()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+
+  try {
+    let ret = SpecialPowers.unwrap(getTableEditor().getFirstRow(undefined));
+    ok(false, "nsITableEditor.getFirstRow(undefined) should cause throwing an exception");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstRow(undefined) should cause throwing an exception");
+  }
+
+  try {
+    let ret = SpecialPowers.unwrap(getTableEditor().getFirstRow(null));
+    ok(false, "nsITableEditor.getFirstRow(null) should cause throwing an exception");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstRow(null) should cause throwing an exception");
+  }
+
+  try {
+    let ret = SpecialPowers.unwrap(getTableEditor().getFirstRow(editor));
+    ok(false, "nsITableEditor.getFirstRow() should cause throwing an exception if given node is not in <table>");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstRow() should cause throwing an exception if given node is not in <table>");
+  }
+
+  // Set id to "test" for the argument for getFirstRow().
+  // Set id to "expected" for the expected <tr> element result (if there is).
+  // Set class of <table> to "hasAnonymousRow" if it does not has <tr> but will be anonymous <tr> element is created.
+  kTests = [
+    '<table id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td>cell1-1</td><td id="test">cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr id="test"><td>cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="test">cell2-1</td><td>cell2-2</td></tr></table>',
+    '<table><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="test">cell2-2</td></tr></table>',
+    '<table><tbody id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><tbody><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><thead id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></thead></table>',
+    '<table><thead><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></thead></table>',
+    '<table><tfoot id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></tfoot></table>',
+    '<table><tfoot><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></tfoot></table>',
+    '<table><thead id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr></thead><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><thead><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr></thead><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><thead><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr></thead><tbody><tr><td id="test">cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><tfoot id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr></tfoot><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><tfoot><tr id="expected"><td id="test">cell1-1</td><td>cell1-2</td></tr></tfoot><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><tfoot><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr></tfoot><tbody><tr><td id="test">cell2-1</td><td>cell2-2</td></tr></tbody></table>',
+    '<table><tr><td><table id="test"><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table></td></tr></table>',
+    '<table><tr><td><table><tr id="expected"><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="test">cell2-1</td><td>cell2-2</td></tr></table></td></tr></table>',
+    '<table id="test"></table>',
+    '<table><caption id="test">table-caption</caption></table>',
+    '<table><caption>table-caption</caption><tr id="expected"><td id="test">cell</td></tr></table>',
+    '<table class="hasAnonymousRow"><td id="test">cell</td></table>',
+    '<table class="hasAnonymousRow"><td>cell-1</td><td id="test">cell-2</td></table>',
+    '<table><tr><td><table id="test"></table></td></tr></table>',
+    '<table><tr><td><table><caption id="test">table-caption</caption></table></td></tr></table>',
+    '<table><tr><td><table class="hasAnonymousRow"><td id="test">cell</td></table></td></tr></table>',
+    '<table><tr><td><table class="hasAnonymousRow"><td>cell-1</td><td id="test">cell-2</td></table></td></tr></table>',
+    '<table><tr id="expected"><td><p id="test">paragraph</p></td></tr></table>',
+  ]
+
+  for (const kTest of kTests) {
+    editor.innerHTML = kTest;
+    let firstRow = SpecialPowers.unwrap(getTableEditor().getFirstRow(document.getElementById("test")));
+    if (document.getElementById("expected")) {
+      is(firstRow.tagName, "TR", `Result should be a <tr>: ${kTest}`);
+      is(firstRow.getAttribute("id"), "expected", `Result should be the first <tr> element in the <table>: ${kTest}`);
+    } else if (document.querySelector(".hasAnonymousRow")) {
+      is(firstRow.tagName, "TR", `Result should be a <tr>: ${kTest}`);
+      is(firstRow, document.querySelector(".hasAnonymousRow tr"), `Result should be the anonymous <tr> element in the <table>: ${kTest}`);
+    } else {
+      is(firstRow, null, `Result should be null if there is no <tr> element in the <table>: ${kTest}`);
+    }
+  }
+
+  SimpleTest.finish();
+});
+
+function getTableEditor() {
+  var Ci = SpecialPowers.Ci;
+  var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
+}
+
+</script>
+</body>
+
+</html>
--- a/editor/libeditor/tests/test_resizers_resizing_elements.html
+++ b/editor/libeditor/tests/test_resizers_resizing_elements.html
@@ -20,16 +20,18 @@
 <script type="application/javascript">
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(async function() {
   document.execCommand("enableObjectResizing", false, true);
   ok(document.queryCommandState("enableObjectResizing"),
      "Object resizer should be enabled by the call of execCommand");
+  // Disable inline-table-editing UI for this test.
+  document.execCommand("enableInlineTableEditing", false, false);
 
   let outOfEditor = document.getElementById("clickaway");
 
   function cancel(e) { e.stopPropagation(); }
   let content = document.getElementById("content");
   content.addEventListener("mousedown", cancel);
   content.addEventListener("mousemove", cancel);
   content.addEventListener("mouseup", cancel);
@@ -214,27 +216,28 @@ SimpleTest.waitForFocus(async function()
     },
     { description: "Resiziers for absolute positioned <div>",
       innerHTML: "<div id=\"target\" style=\"position: absolute; top: 50px; left: 50px;\">positioned</div>",
       mayPreserveRatio: false,
       isAbsolutePosition: true,
     },
   ];
 
-  // Resizers for absolute positioned element are available only when
-  // enableAbsolutePositionEditing is enabled.  So, let's enable it
-  // during testing resizers for absolute positioned elements.
+  // Resizers for absolute positioned element and table element are available
+  // only when enableAbsolutePositionEditing or enableInlineTableEditing is
+  // enabled for each.  So, let's enable them during testing resizers for 
+  // absolute positioned elements or table elements.
   await SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", false]]});
   for (const kTest of kTests) {
-    document.execCommand("enableAbsolutePositionEditing", false, kTests.isAbsolutePosition);
+    document.execCommand("enableAbsolutePositionEditing", false, kTest.isAbsolutePosition);
     await doTest(kTest.description, false, kTest.innerHTML);
   }
   await SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", true]]});
   for (const kTest of kTests) {
-    document.execCommand("enableAbsolutePositionEditing", false, kTests.isAbsolutePosition);
+    document.execCommand("enableAbsolutePositionEditing", false, kTest.isAbsolutePosition);
     await doTest(kTest.description, kTest.mayPreserveRatio, kTest.innerHTML);
   }
   content.innerHTML = "";
   SimpleTest.finish();
 });
 </script>
 </pre>
 </body>
--- a/editor/nsITableEditor.idl
+++ b/editor/nsITableEditor.idl
@@ -198,36 +198,35 @@ interface nsITableEditor : nsISupports
   void getCellDataAt(in Element aTable,
                      in long  aRowIndex, in long  aColIndex,
                      out Element aCell,
                      out long  aStartRowIndex, out long  aStartColIndex,
                      out long  aRowSpan, out long  aColSpan,
                      out long  aActualRowSpan, out long  aActualColSpan,
                      out boolean aIsSelected);
 
-  /** Get the first row element in a table
-    *
-    * @return            The row at the requested index
-    *                    Returns null if there are no rows in table
-    * (in C++ returns: NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found
-    *  passes NS_SUCCEEDED macro)
-    */
-  Node getFirstRow(in Element aTableElement);
-
-  /** Get the next row element starting the search from aTableElement
-    *
-    * @param aTableElement Any TR or child-of-TR element in the document
-    *
-    * @return            The row to start search from
-    *                    and the row returned from the search
-    *                    Returns null if there isn't another row
-    * (in C++ returns: NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found
-    *  passes NS_SUCCEEDED macro)
-    */
-  Node getNextRow(in Node aTableElement);
+  /**
+   * getFirstRow() returns first <tr> element in a <table> element.
+   *
+   * @param aTableOrElementInTable  If a <table> element, returns its first
+   *                                <tr> element.
+   *                                If another element, looks for nearest
+   *                                ancestor <table> element first.  Then,
+   *                                return its first <tr> element.
+   * @return                        <tr> element in the <table> element.
+   *                                If <table> element is not found, this
+   *                                throws an exception.
+   *                                If there is a <table> element but it
+   *                                does not have <tr> elements, returns
+   *                                null without throwing exception.
+   *                                Note that this may return anonymous <tr>
+   *                                element if <table> has one or more cells
+   *                                but <tr> element is not in the source.
+   */
+  Element getFirstRow(in Element aTableElement);
 
   /** Preferred direction to search for neighboring cell
     * when trying to locate a cell to place caret in after
     * a table editing action.
     * Used for aDirection param in SetSelectionAfterTableEdit
     */
 
   /** Examine the current selection and find
--- a/editor/reftests/reftest.list
+++ b/editor/reftests/reftest.list
@@ -130,12 +130,12 @@ needs-focus == spellcheck-contenteditabl
 == spellcheck-contenteditable-attr-dynamic-override.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic-override-inherit.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-property-dynamic-override.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-property-dynamic-override-inherit.html spellcheck-contenteditable-disabled-ref.html
 == 911201.html 911201-ref.html
 needs-focus == 969773.html 969773-ref.html
 fuzzy-if(skiaContent,0-1,0-220) == 997805.html 997805-ref.html
 fuzzy-if(skiaContent,0-1,0-220) skip-if(verify&&OSX) == 1088158.html 1088158-ref.html
-needs-focus == 1443902-1.html 1443902-1-ref.html
-needs-focus == 1443902-2.html 1443902-2-ref.html
-needs-focus == 1443902-3.html 1443902-3-ref.html
-needs-focus == 1443902-4.html 1443902-4-ref.html
+fuzzy-if(Android,0-1,0-1) needs-focus == 1443902-1.html 1443902-1-ref.html
+fuzzy-if(Android,0-1,0-1) needs-focus == 1443902-2.html 1443902-2-ref.html
+fuzzy-if(Android,0-1,0-1) needs-focus == 1443902-3.html 1443902-3-ref.html
+fuzzy-if(Android,0-1,0-1) needs-focus == 1443902-4.html 1443902-4-ref.html
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -12,16 +12,17 @@
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MathAlgorithms.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Unused.h"
 #include "mozilla/PodOperations.h"
 
 #include "nsCOMPtr.h"
 #include "nsBlockFrame.h"
@@ -8551,17 +8552,18 @@ nsTextFrame::AddInlineMinISizeForFlow(gf
       aData->OptionallyBreak();
     }
     aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
     aData->mTrailingWhitespace = 0;
     return;
   }
 
   // If overflow-wrap is break-word, we can wrap everywhere.
-  if (textStyle->WordCanWrap(this)) {
+  if (StaticPrefs::layout_css_overflow_break_intrinsic_size() &&
+      textStyle->WordCanWrap(this)) {
     aData->OptionallyBreak();
     aData->mCurrentLine +=
       textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
     aData->mTrailingWhitespace = 0;
     aData->mAtStartOfLine = false;
     aData->OptionallyBreak();
     return;
   }
--- a/layout/tools/reftest/README.txt
+++ b/layout/tools/reftest/README.txt
@@ -109,32 +109,49 @@ 2. A test item
 
       slow-if(condition) If the condition is met, the test is treated as if
                          'slow' had been specified.  This is useful for tests
                          which are slow only on particular platforms (e.g. a
                          test which exercised out-of-memory behavior might be
                          fast on a 32-bit system but inordinately slow on a
                          64-bit system).
 
-      fuzzy(maxDiff,maxPixelCount)
       fuzzy(minDiff-maxDiff,minPixelCount-maxPixelCount)
           This allows a test to pass if the pixel value differences are between
           minDiff and maxDiff, inclusive, and the total number of different
           pixels is between minPixelCount and maxPixelCount, inclusive.
-          If the minDiff and/or minPixelCount values are not specified, they
-          are assumed to be zero.
           It can also be used with '!=' to ensure that the difference is
           outside the specified interval. Note that with '!=' tests the
           minimum bounds of the ranges must be zero.
 
-      fuzzy-if(condition,maxDiff,diffCount)
+          Fuzzy tends to be used for two different sorts of cases.  The main
+          case is tests that are expected to be equal, but actually fail in a
+          minor way (e.g., an antialiasing difference), and we want to ensure
+          that the test doesn't regress further so we don't want to mark the
+          test as failing.  For these cases, test annotations should be the
+          tightest bounds possible:  if the behavior is entirely deterministic
+          this means a range like fuzzy(1-1,8-8), and if at all possible, the
+          ranges should not include 0.  In cases where the test only sometimes
+          fails, this unfortunately requires using 0 in both ranges, which
+          means that we won't get reports of an unexpected pass if the problem
+          is fixed (allowing us to remove the fuzzy() annotation and expect
+          the test to pass from then on).
+
+          The second case where fuzzy is used is tests that are supposed
+          to allow some amount of variability (i.e., tests where the
+          specification allows variability such that we can't assert
+          that all pixels are the same).  Such tests should generally be
+          avoided (for example, by covering up the pixels that can vary
+          with another element), but when they are needed, the ranges in
+          the fuzzy() annotation should generally include 0.
+
       fuzzy-if(condition,minDiff-maxDiff,minPixelCount-maxPixelCount)
           If the condition is met, the test is treated as if 'fuzzy' had been
           specified. This is useful if there are differences on particular
-          platforms.
+          platforms.  See fuzzy() above.
 
       require-or(cond1&&cond2&&...,fallback)
           Require some particular setup be performed or environmental
           condition(s) made true (eg setting debug mode) before the test
           is run. If any condition is unknown, unimplemented, or fails,
           revert to the fallback failure-type.
           Example: require-or(debugMode,skip)
 
--- a/layout/tools/reftest/manifest.jsm
+++ b/layout/tools/reftest/manifest.jsm
@@ -184,22 +184,22 @@ function ReadManifest(aURL, aFilter)
             } else if (item == "silentfail") {
                 cond = false;
                 allow_silent_fail = true;
             } else if ((m = item.match(RE_PREF_ITEM))) {
                 cond = false;
                 if (!AddPrefSettings(m[1], m[2], m[3], sandbox, testPrefSettings, refPrefSettings)) {
                     throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
                 }
-            } else if ((m = item.match(/^fuzzy\((\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
+            } else if ((m = item.match(/^fuzzy\((\d+)-(\d+),(\d+)-(\d+)\)$/))) {
               cond = false;
               expected_status = EXPECTED_FUZZY;
               fuzzy_delta = ExtractRange(m, 1);
               fuzzy_pixels = ExtractRange(m, 3);
-            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
+            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+)-(\d+),(\d+)-(\d+)\)$/))) {
               cond = false;
               if (Cu.evalInSandbox("(" + m[1] + ")", sandbox)) {
                 expected_status = EXPECTED_FUZZY;
                 fuzzy_delta = ExtractRange(m, 2);
                 fuzzy_pixels = ExtractRange(m, 4);
               }
             } else if (item == "chaos-mode") {
                 cond = false;
@@ -584,26 +584,20 @@ function AddPrefSettings(aWhere, aPrefNa
         }
         if (aWhere != "test-") {
             aRefPrefSettings.push(setting);
         }
     }
     return true;
 }
 
-function ExtractRange(matches, startIndex, defaultMin = 0) {
-    if (matches[startIndex + 1] === undefined) {
-        return {
-            min: defaultMin,
-            max: Number(matches[startIndex])
-        };
-    }
+function ExtractRange(matches, startIndex) {
     return {
         min: Number(matches[startIndex]),
-        max: Number(matches[startIndex + 1].substring(1))
+        max: Number(matches[startIndex + 1])
     };
 }
 
 function ServeTestBase(aURL, depth) {
     var listURL = aURL.QueryInterface(Ci.nsIFileURL);
     var directory = listURL.file.parent;
 
     // Allow serving a tree that's an ancestor of the directory containing
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -429,16 +429,23 @@ VARCACHE_PREF(
 
 // Is overflow: -moz-scrollbars-* value enabled?
 VARCACHE_PREF(
   "layout.css.overflow.moz-scrollbars.enabled",
    layout_css_overflow_moz_scrollbars_enabled,
   bool, false
 )
 
+// Does overflow-break: break-word affect intrinsic size?
+VARCACHE_PREF(
+  "layout.css.overflow-break.intrinsic-size",
+   layout_css_overflow_break_intrinsic_size,
+  bool, false
+)
+
 //---------------------------------------------------------------------------
 // JavaScript prefs
 //---------------------------------------------------------------------------
 
 // nsJSEnvironmentObserver observes the memory-pressure notifications and
 // forces a garbage collection and cycle collection when it happens, if the
 // appropriate pref is set.
 #ifdef ANDROID
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -840,18 +840,18 @@ macro_rules! malloc_size_of_is_0(
                 0
             }
         }
         )+
     );
 );
 
 malloc_size_of_is_0!(bool, char, str);
-malloc_size_of_is_0!(u8, u16, u32, u64, usize);
-malloc_size_of_is_0!(i8, i16, i32, i64, isize);
+malloc_size_of_is_0!(u8, u16, u32, u64, u128, usize);
+malloc_size_of_is_0!(i8, i16, i32, i64, i128, isize);
 malloc_size_of_is_0!(f32, f64);
 
 malloc_size_of_is_0!(std::sync::atomic::AtomicBool);
 malloc_size_of_is_0!(std::sync::atomic::AtomicIsize, std::sync::atomic::AtomicUsize);
 
 malloc_size_of_is_0!(Range<u8>, Range<u16>, Range<u32>, Range<u64>, Range<usize>);
 malloc_size_of_is_0!(Range<i8>, Range<i16>, Range<i32>, Range<i64>, Range<isize>);
 malloc_size_of_is_0!(Range<f32>, Range<f64>);
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-text/overflow-wrap/overflow-wrap-min-content-size-001.html.ini
@@ -0,0 +1,2 @@
+[overflow-wrap-min-content-size-001.html]
+  prefs: [layout.css.overflow-break.intrinsic-size:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-text/overflow-wrap/overflow-wrap-min-content-size-002.html.ini
@@ -0,0 +1,2 @@
+[overflow-wrap-min-content-size-002.html]
+  prefs: [layout.css.overflow-break.intrinsic-size:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-text/overflow-wrap/overflow-wrap-min-content-size-003.html.ini
@@ -0,0 +1,2 @@
+[overflow-wrap-min-content-size-003.html]
+  prefs: [layout.css.overflow-break.intrinsic-size:true]
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -1102,61 +1102,28 @@ this.VideoControlsImplPageWidget = class
             { opacity: 1 }
           ],
           options: {
             duration: 1050
           }
         }
       },
 
-      // Polyfill animation.finished promise, also invalidate
-      // the previous promise.
-      // Remove when the platform implementation ships.
-      // (currently behind dom.animations-api.core.enabled)
-      installFinishedPromisePolyfill(animation) {
-        let handler = {
-          handleEvent(evt) {
-            animation.removeEventListener("finish", this);
-            animation.removeEventListener("cancel", this);
-            if (evt.type == "finish" &&
-                this === animation.finished) {
-              this.fn();
-            }
-          },
-          then(fn) {
-            this.fn = fn;
-          }
-        };
-        // Note that handler is not a real Promise.
-        // All it offered is a then() method to register a callback
-        // to be triggered at the right time.
-        Object.defineProperty(animation, "finished", { value: handler, configurable: true });
-        animation.addEventListener("finish", handler);
-        animation.addEventListener("cancel", handler);
-      },
-
       startFade(element, fadeIn, immediate = false) {
         let animationProp =
           this.animationProps[element.id];
         if (!animationProp) {
           throw new Error("Element " + element.id +
             " has no transition. Toggle the hidden property directly.");
         }
 
         let animation = this.animationMap.get(element);
         if (!animation) {
-          // Create the animation object but don't start it.
-          // To be replaced with the following when the constructors ship
-          // (currently behind dom.animations-api.core.enabled)
-          /*
           animation = new this.window.Animation(new this.window.KeyframeEffect(
             element, animationProp.keyframes, animationProp.options));
-          */
-          animation = element.animate(animationProp.keyframes, animationProp.options);
-          animation.cancel();
 
           this.animationMap.set(element, animation);
         }
 
         if (fadeIn) {
           if (element == this.controlBar) {
             this.controlsSpacer.removeAttribute("hideCursor");
           }
@@ -1188,17 +1155,16 @@ this.VideoControlsImplPageWidget = class
         }
 
         element.classList.toggle("fadeout", !fadeIn);
         element.classList.toggle("fadein", fadeIn);
         let finishedPromise;
         if (!immediate) {
           animation.playbackRate = fadeIn ? 1 : -1;
           animation.play();
-          this.installFinishedPromisePolyfill(animation);
           finishedPromise = animation.finished;
         } else {
           animation.cancel();
           finishedPromise = Promise.resolve();
         }
         finishedPromise.then(() => {
           if (element == this.controlBar) {
             this.onControlBarAnimationFinished();
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1123,42 +1123,16 @@
             { opacity: 1 }
           ],
           options: {
             duration: 1050
           }
         }
       },
 
-      // Polyfill animation.finished promise, also invalidate
-      // the previous promise.
-      // Remove when the platform implementation ships.
-      // (currently behind dom.animations-api.core.enabled)
-      installFinishedPromisePolyfill(animation) {
-        let handler = {
-          handleEvent(evt) {
-            animation.removeEventListener("finish", this);
-            animation.removeEventListener("cancel", this);
-            if (evt.type == "finish" &&
-                this === animation.finished) {
-              this.fn();
-            }
-          },
-          then(fn) {
-            this.fn = fn;
-          }
-        };
-        // Note that handler is not a real Promise.
-        // All it offered is a then() method to register a callback
-        // to be triggered at the right time.
-        animation.finished = handler;
-        animation.addEventListener("finish", handler);
-        animation.addEventListener("cancel", handler);
-      },
-
       startFade(element, fadeIn, immediate = false) {
         // Bug 493523, the scrubber doesn't call valueChanged while hidden,
         // so our dependent state (eg, timestamp in the thumb) will be stale.
         // As a workaround, update it manually when it first becomes unhidden.
         if (element == this.controlBar && fadeIn && element.hidden) {
           this.scrubber.value = this.video.currentTime * 1000;
         }
 
@@ -1166,25 +1140,18 @@
           this.animationProps[element.getAttribute("anonid")];
         if (!animationProp) {
           throw new Error("Element " + element.getAttribute("anonid") +
             " has no transition. Toggle the hidden property directly.");
         }
 
         let animation = this.animationMap.get(element);
         if (!animation) {
-          // Create the animation object but don't start it.
-          // To be replaced with the following when the constructors ship
-          // (currently behind dom.animations-api.core.enabled)
-          /*
           animation = new Animation(new KeyframeEffect(
             element, animationProp.keyframes, animationProp.options));
-          */
-          animation = element.animate(animationProp.keyframes, animationProp.options);
-          animation.cancel();
 
           this.animationMap.set(element, animation);
         }
 
         if (fadeIn) {
           // hidden state should be controlled by adjustControlSize
           if (element.isAdjustableControl && element.hiddenByAdjustment) {
             return;
@@ -1214,17 +1181,16 @@
         }
 
         element.classList.toggle("fadeout", !fadeIn);
         element.classList.toggle("fadein", fadeIn);
         let finishedPromise;
         if (!immediate) {
           animation.playbackRate = fadeIn ? 1 : -1;
           animation.play();
-          this.installFinishedPromisePolyfill(animation);
           finishedPromise = animation.finished;
         } else {
           animation.cancel();
           finishedPromise = Promise.resolve();
         }
         finishedPromise.then(() => {
           if (element == this.controlBar) {
             this.onControlBarAnimationFinished();
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -61,26 +61,16 @@ audio > xul|videocontrols,
 
 .touch .controlBar {
   /* Do not delete: these variables are accessed by JavaScript directly.
      see videocontrols.xml and search for |-width|. */
   --scrubberStack-width: 84px;
   --volumeStack-width: 64px;
 }
 
-/*
-  XXX this is needed because of bug 1354501.
-  Can be removed when the bug is fixed, or when we move away from
-  the finish event to the finished promise.
-  (currently behind dom.animations-api.core.enabled)
-*/
-.fadeout {
-  opacity: 0;
-}
-
 .controlsContainer [hidden],
 .controlBar[hidden] {
   display: none;
 }
 
 .controlBar[size="hidden"] {
   display: none;
 }
--- a/widget/cocoa/nsPrintSettingsX.mm
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -156,17 +156,17 @@ NS_IMETHODIMP nsPrintSettingsX::WritePag
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   PMPageFormat pageFormat = GetPMPageFormat();
   if (pageFormat == kPMNoPageFormat)
     return NS_ERROR_NOT_INITIALIZED;
 
   NSData* data = nil;
-  OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLCompressed);
+  OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLDefault);
   if (err != noErr)
     return NS_ERROR_FAILURE;
 
   nsAutoCString encodedData;
   encodedData.Adopt(PL_Base64Encode((char*)[data bytes], [data length], nullptr));
   if (!encodedData.get())
     return NS_ERROR_OUT_OF_MEMORY;