diff --git a/application/palemoon/config/version.txt b/application/palemoon/config/version.txt index e813afb897..b46e6eba85 100644 --- a/application/palemoon/config/version.txt +++ b/application/palemoon/config/version.txt @@ -1 +1 @@ -28.9.3a1 \ No newline at end of file +28.9.4a1 \ No newline at end of file diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index c4e56f3fbe..2383c951a6 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -15,7 +15,9 @@ #include "nsIStyleSheetLinkingElement.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLSlotElement.h" +#include "mozilla/dom/StyleSheetList.h" #include "nsXBLPrototypeBinding.h" +#include "mozilla/BasicEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h index a24c8138e0..21c6e1733d 100644 --- a/dom/base/ShadowRoot.h +++ b/dom/base/ShadowRoot.h @@ -12,8 +12,9 @@ #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsIContentInlines.h" +#include "nsIdentifierMapEntry.h" #include "nsTHashtable.h" -#include "nsDocument.h" +#include "nsXBLBinding.h" class nsIAtom; class nsIContent; diff --git a/dom/base/moz.build b/dom/base/moz.build index 5acb49d4ea..ab0f0e0abc 100755 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -86,6 +86,7 @@ EXPORTS += [ 'nsIContentInlines.h', 'nsIContentIterator.h', 'nsIContentSerializer.h', + 'nsIdentifierMapEntry.h', 'nsIDocument.h', 'nsIDocumentInlines.h', 'nsIDocumentObserver.h', diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 6520d905d1..6baf40270a 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -19,6 +19,7 @@ #include "nsWeakReference.h" #include "nsWeakPtr.h" #include "nsTArray.h" +#include "nsIdentifierMapEntry.h" #include "nsIDOMDocument.h" #include "nsIDOMDocumentXBL.h" #include "nsStubDocumentObserver.h" @@ -130,151 +131,6 @@ public: } // namespace dom } // namespace mozilla -/** - * Right now our identifier map entries contain information for 'name' - * and 'id' mappings of a given string. This is so that - * nsHTMLDocument::ResolveName only has to do one hash lookup instead - * of two. It's not clear whether this still matters for performance. - * - * We also store the document.all result list here. This is mainly so that - * when all elements with the given ID are removed and we remove - * the ID's nsIdentifierMapEntry, the document.all result is released too. - * Perhaps the document.all results should have their own hashtable - * in nsHTMLDocument. - */ -class nsIdentifierMapEntry : public nsStringHashKey -{ -public: - typedef mozilla::dom::Element Element; - typedef mozilla::net::ReferrerPolicy ReferrerPolicy; - - explicit nsIdentifierMapEntry(const nsAString& aKey) : - nsStringHashKey(&aKey), mNameContentList(nullptr) - { - } - explicit nsIdentifierMapEntry(const nsAString* aKey) : - nsStringHashKey(aKey), mNameContentList(nullptr) - { - } - nsIdentifierMapEntry(const nsIdentifierMapEntry& aOther) : - nsStringHashKey(&aOther.GetKey()) - { - NS_ERROR("Should never be called"); - } - ~nsIdentifierMapEntry(); - - void AddNameElement(nsINode* aDocument, Element* aElement); - void RemoveNameElement(Element* aElement); - bool IsEmpty(); - nsBaseContentList* GetNameContentList() { - return mNameContentList; - } - bool HasNameElement() const { - return mNameContentList && mNameContentList->Length() != 0; - } - - /** - * Returns the element if we know the element associated with this - * id. Otherwise returns null. - */ - Element* GetIdElement(); - /** - * Returns the list of all elements associated with this id. - */ - const nsTArray& GetIdElements() const { - return mIdContentList; - } - /** - * If this entry has a non-null image element set (using SetImageElement), - * the image element will be returned, otherwise the same as GetIdElement(). - */ - Element* GetImageIdElement(); - /** - * Append all the elements with this id to aElements - */ - void AppendAllIdContent(nsCOMArray* aElements); - /** - * This can fire ID change callbacks. - * @return true if the content could be added, false if we failed due - * to OOM. - */ - bool AddIdElement(Element* aElement); - /** - * This can fire ID change callbacks. - */ - void RemoveIdElement(Element* aElement); - /** - * Set the image element override for this ID. This will be returned by - * GetIdElement(true) if non-null. - */ - void SetImageElement(Element* aElement); - bool HasIdElementExposedAsHTMLDocumentProperty(); - - bool HasContentChangeCallback() { return mChangeCallbacks != nullptr; } - void AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback, - void* aData, bool aForImage); - void RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback, - void* aData, bool aForImage); - - /** - * Remove all elements and notify change listeners. - */ - void ClearAndNotify(); - - void Traverse(nsCycleCollectionTraversalCallback* aCallback); - - struct ChangeCallback { - nsIDocument::IDTargetObserver mCallback; - void* mData; - bool mForImage; - }; - - struct ChangeCallbackEntry : public PLDHashEntryHdr { - typedef const ChangeCallback KeyType; - typedef const ChangeCallback* KeyTypePointer; - - explicit ChangeCallbackEntry(const ChangeCallback* aKey) : - mKey(*aKey) { } - ChangeCallbackEntry(const ChangeCallbackEntry& toCopy) : - mKey(toCopy.mKey) { } - - KeyType GetKey() const { return mKey; } - bool KeyEquals(KeyTypePointer aKey) const { - return aKey->mCallback == mKey.mCallback && - aKey->mData == mKey.mData && - aKey->mForImage == mKey.mForImage; - } - - static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } - static PLDHashNumber HashKey(KeyTypePointer aKey) - { - return mozilla::HashGeneric(aKey->mCallback, aKey->mData); - } - enum { ALLOW_MEMMOVE = true }; - - ChangeCallback mKey; - }; - - size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - -private: - void FireChangeCallbacks(Element* aOldElement, Element* aNewElement, - bool aImageOnly = false); - - // empty if there are no elements with this ID. - // The elements are stored as weak pointers. - nsTArray mIdContentList; - RefPtr mNameContentList; - nsAutoPtr > mChangeCallbacks; - RefPtr mImageElement; -}; - -namespace mozilla { -namespace dom { - -} // namespace dom -} // namespace mozilla - class nsDocHeaderData { public: diff --git a/dom/base/nsIdentifierMapEntry.h b/dom/base/nsIdentifierMapEntry.h new file mode 100644 index 0000000000..fce506cef3 --- /dev/null +++ b/dom/base/nsIdentifierMapEntry.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +/* + * Base class for all our document implementations. + */ + +#ifndef nsIdentifierMapEntry_h +#define nsIdentifierMapEntry_h + +#include "PLDHashTable.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/dom/Element.h" +#include "mozilla/net/ReferrerPolicy.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsContentList.h" +#include "nsIAtom.h" +#include "nsIDocument.h" +#include "nsTArray.h" +#include "nsTHashtable.h" + +class nsIContent; + +/** + * Right now our identifier map entries contain information for 'name' + * and 'id' mappings of a given string. This is so that + * nsHTMLDocument::ResolveName only has to do one hash lookup instead + * of two. It's not clear whether this still matters for performance. + * + * We also store the document.all result list here. This is mainly so that + * when all elements with the given ID are removed and we remove + * the ID's nsIdentifierMapEntry, the document.all result is released too. + * Perhaps the document.all results should have their own hashtable + * in nsHTMLDocument. + */ +class nsIdentifierMapEntry : public nsStringHashKey +{ +public: + typedef mozilla::dom::Element Element; + typedef mozilla::net::ReferrerPolicy ReferrerPolicy; + + explicit nsIdentifierMapEntry(const nsAString& aKey) : + nsStringHashKey(&aKey), mNameContentList(nullptr) + { + } + explicit nsIdentifierMapEntry(const nsAString* aKey) : + nsStringHashKey(aKey), mNameContentList(nullptr) + { + } + nsIdentifierMapEntry(const nsIdentifierMapEntry& aOther) : + nsStringHashKey(&aOther.GetKey()) + { + NS_ERROR("Should never be called"); + } + ~nsIdentifierMapEntry(); + + void AddNameElement(nsINode* aDocument, Element* aElement); + void RemoveNameElement(Element* aElement); + bool IsEmpty(); + nsBaseContentList* GetNameContentList() { + return mNameContentList; + } + bool HasNameElement() const { + return mNameContentList && mNameContentList->Length() != 0; + } + + /** + * Returns the element if we know the element associated with this + * id. Otherwise returns null. + */ + Element* GetIdElement(); + /** + * Returns the list of all elements associated with this id. + */ + const nsTArray& GetIdElements() const { + return mIdContentList; + } + /** + * If this entry has a non-null image element set (using SetImageElement), + * the image element will be returned, otherwise the same as GetIdElement(). + */ + Element* GetImageIdElement(); + /** + * Append all the elements with this id to aElements + */ + void AppendAllIdContent(nsCOMArray* aElements); + /** + * This can fire ID change callbacks. + * @return true if the content could be added, false if we failed due + * to OOM. + */ + bool AddIdElement(Element* aElement); + /** + * This can fire ID change callbacks. + */ + void RemoveIdElement(Element* aElement); + /** + * Set the image element override for this ID. This will be returned by + * GetIdElement(true) if non-null. + */ + void SetImageElement(Element* aElement); + bool HasIdElementExposedAsHTMLDocumentProperty(); + + bool HasContentChangeCallback() { return mChangeCallbacks != nullptr; } + void AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback, + void* aData, bool aForImage); + void RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback, + void* aData, bool aForImage); + + /** + * Remove all elements and notify change listeners. + */ + void ClearAndNotify(); + + void Traverse(nsCycleCollectionTraversalCallback* aCallback); + + struct ChangeCallback { + nsIDocument::IDTargetObserver mCallback; + void* mData; + bool mForImage; + }; + + struct ChangeCallbackEntry : public PLDHashEntryHdr { + typedef const ChangeCallback KeyType; + typedef const ChangeCallback* KeyTypePointer; + + explicit ChangeCallbackEntry(const ChangeCallback* aKey) : + mKey(*aKey) { } + ChangeCallbackEntry(const ChangeCallbackEntry& toCopy) : + mKey(toCopy.mKey) { } + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { + return aKey->mCallback == mKey.mCallback && + aKey->mData == mKey.mData && + aKey->mForImage == mKey.mForImage; + } + + static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return mozilla::HashGeneric(aKey->mCallback, aKey->mData); + } + enum { ALLOW_MEMMOVE = true }; + + ChangeCallback mKey; + }; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +private: + void FireChangeCallbacks(Element* aOldElement, Element* aNewElement, + bool aImageOnly = false); + + // empty if there are no elements with this ID. + // The elements are stored as weak pointers. + nsTArray mIdContentList; + RefPtr mNameContentList; + nsAutoPtr > mChangeCallbacks; + RefPtr mImageElement; +}; + +#endif // #ifndef nsIdentifierMapEntry_h diff --git a/dom/bindings/SimpleGlobalObject.cpp b/dom/bindings/SimpleGlobalObject.cpp index 88710f7d95..34e204dc79 100644 --- a/dom/bindings/SimpleGlobalObject.cpp +++ b/dom/bindings/SimpleGlobalObject.cpp @@ -13,6 +13,7 @@ #include "nsNullPrincipal.h" #include "nsThreadUtils.h" #include "nsContentUtils.h" +#include "nsWrapperCacheInlines.h" #include "xpcprivate.h" diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 6b914372b0..69f7a3ba2b 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -67,6 +67,7 @@ #include "mozInlineSpellChecker.h" #include "nsDocShell.h" #include "nsIConsoleListener.h" +#include "nsIContentViewer.h" #include "nsICycleCollectorListener.h" #include "nsIIdlePeriod.h" #include "nsIDragService.h" diff --git a/dom/worklet/WorkletGlobalScope.cpp b/dom/worklet/WorkletGlobalScope.cpp index 8c05a0abe3..430e12b766 100644 --- a/dom/worklet/WorkletGlobalScope.cpp +++ b/dom/worklet/WorkletGlobalScope.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/WorkletGlobalScopeBinding.h" #include "mozilla/dom/Console.h" #include "nsContentUtils.h" +#include "nsWrapperCacheInlines.h" namespace mozilla { namespace dom { diff --git a/editor/libeditor/CSSEditUtils.cpp b/editor/libeditor/CSSEditUtils.cpp index d8146ca654..dd15a8730a 100644 --- a/editor/libeditor/CSSEditUtils.cpp +++ b/editor/libeditor/CSSEditUtils.cpp @@ -17,6 +17,9 @@ #include "nsCOMPtr.h" #include "nsColor.h" #include "nsComputedDOMStyle.h" +#ifdef DEBUG +#include "nsDocument.h" +#endif #include "nsDebug.h" #include "nsDependentSubstring.h" #include "nsError.h" diff --git a/editor/libeditor/HTMLAnonymousNodeEditor.cpp b/editor/libeditor/HTMLAnonymousNodeEditor.cpp index 48f20fd040..798f5c330f 100644 --- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp +++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp @@ -11,6 +11,9 @@ #include "nsCOMPtr.h" #include "nsComputedDOMStyle.h" #include "nsDebug.h" +#ifdef DEBUG +#include "nsDocument.h" +#endif #include "nsError.h" #include "nsGkAtoms.h" #include "nsIAtom.h" diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h index 4bbb6fdf3c..95d3c0375b 100644 --- a/editor/libeditor/HTMLEditUtils.h +++ b/editor/libeditor/HTMLEditUtils.h @@ -7,6 +7,10 @@ #define HTMLEditUtils_h #include +#ifdef DEBUG +// Used by various files for debug logging; included here to reduce duplication +#include "nsDocument.h" +#endif class nsIDOMNode; class nsINode; diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index 9894ce8f45..68a7be21ed 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -50,6 +50,7 @@ #include "nsGkAtoms.h" #include "nsIThreadInternal.h" #include "nsINetworkPredictor.h" +#include "nsITimedChannel.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/URL.h" #include "mozilla/AsyncEventDispatcher.h" diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index b406fded58..95f599be46 100644 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -521,6 +521,8 @@ WebrtcVideoConduit::DeleteStreams() mVideoCodecStat->EndOfCallStats(); } mVideoCodecStat = nullptr; + //This does Release AudioConduit before mPtrViEBase set nullptr. + SyncTo(nullptr); // We can't delete the VideoEngine until all these are released! // And we can't use a Scoped ptr, since the order is arbitrary mPtrViEBase = nullptr; @@ -543,6 +545,11 @@ WebrtcVideoConduit::SyncTo(WebrtcAudioConduit *aConduit) { CSFLogDebug(logTag, "%s Synced to %p", __FUNCTION__, aConduit); + if (!mPtrViEBase) { + // ViEBase has already been released; we no longer have a conduit. + mSyncedTo = nullptr; + return; + } // SyncTo(value) syncs to the AudioConduit, and if already synced replaces // the current sync target. SyncTo(nullptr) cancels any existing sync and // releases the strong ref to AudioConduit. diff --git a/toolkit/components/reader/AboutReader.jsm b/toolkit/components/reader/AboutReader.jsm index 9d9362a0cc..3958af0810 100644 --- a/toolkit/components/reader/AboutReader.jsm +++ b/toolkit/components/reader/AboutReader.jsm @@ -70,8 +70,6 @@ var AboutReader = function(win, articlePromise) { Services.obs.addObserver(this, "inner-window-destroyed", false); - doc.addEventListener("visibilitychange", this); - this._setupStyleDropdown(); this._setupButton("close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close"); @@ -238,14 +236,6 @@ AboutReader.prototype = { } break; - case "devicelight": - this._handleDeviceLight(aEvent.value); - break; - - case "visibilitychange": - this._handleVisibilityChange(); - break; - case "pagehide": // Close the Banners Font-dropdown, cleanup Android BackPressListener. this._closeDropdowns(); @@ -310,10 +300,6 @@ AboutReader.prototype = { const FONT_SIZE_MIN = 1; const FONT_SIZE_MAX = 9; - // Sample text shown in Android UI. - let sampleText = this._doc.querySelector(".font-size-sample"); - sampleText.textContent = gStrings.GetStringFromName("aboutReader.fontTypeSample"); - let currentSize = Services.prefs.getIntPref("reader.font_size"); currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize)); @@ -503,77 +489,8 @@ AboutReader.prototype = { }, true); }, - _handleDeviceLight(newLux) { - // Desired size of the this._luxValues array. - let luxValuesSize = 10; - // Add new lux value at the front of the array. - this._luxValues.unshift(newLux); - // Add new lux value to this._totalLux for averaging later. - this._totalLux += newLux; - - // Don't update when length of array is less than luxValuesSize except when it is 1. - if (this._luxValues.length < luxValuesSize) { - // Use the first lux value to set the color scheme until our array equals luxValuesSize. - if (this._luxValues.length == 1) { - this._updateColorScheme(newLux); - } - return; - } - // Holds the average of the lux values collected in this._luxValues. - let averageLuxValue = this._totalLux / luxValuesSize; - - this._updateColorScheme(averageLuxValue); - // Pop the oldest value off the array. - let oldLux = this._luxValues.pop(); - // Subtract oldLux since it has been discarded from the array. - this._totalLux -= oldLux; - }, - - _handleVisibilityChange() { - let colorScheme = Services.prefs.getCharPref("reader.color_scheme"); - if (colorScheme != "auto") { - return; - } - - // Turn off the ambient light sensor if the page is hidden - this._enableAmbientLighting(!this._doc.hidden); - }, - - // Setup or teardown the ambient light tracking system. - _enableAmbientLighting(enable) { - if (enable) { - this._win.addEventListener("devicelight", this); - this._luxValues = []; - this._totalLux = 0; - } else { - this._win.removeEventListener("devicelight", this); - delete this._luxValues; - delete this._totalLux; - } - }, - - _updateColorScheme(luxValue) { - // Upper bound value for "dark" color scheme beyond which it changes to "light". - let upperBoundDark = 50; - // Lower bound value for "light" color scheme beyond which it changes to "dark". - let lowerBoundLight = 10; - // Threshold for color scheme change. - let colorChangeThreshold = 20; - - // Ignore changes that are within a certain threshold of previous lux values. - if ((this._colorScheme === "dark" && luxValue < upperBoundDark) || - (this._colorScheme === "light" && luxValue > lowerBoundLight)) - return; - - if (luxValue < colorChangeThreshold) - this._setColorScheme("dark"); - else - this._setColorScheme("light"); - }, - _setColorScheme(newColorScheme) { - // "auto" is not a real color scheme - if (this._colorScheme === newColorScheme || newColorScheme === "auto") + if (this._colorScheme === newColorScheme) return; let bodyClasses = this._doc.body.classList; @@ -585,10 +502,8 @@ AboutReader.prototype = { bodyClasses.add(this._colorScheme); }, - // Pref values include "dark", "light", and "auto", which automatically switches - // between light and dark color schemes based on the ambient light level. + // Pref values include "dark", "light", and "sepia". _setColorSchemePref(colorSchemePref) { - this._enableAmbientLighting(colorSchemePref === "auto"); this._setColorScheme(colorSchemePref); AsyncPrefs.set("reader.color_scheme", colorSchemePref); diff --git a/toolkit/components/reader/JSDOMParser.js b/toolkit/components/reader/JSDOMParser.js index ab2f503e1f..2d3d6f156f 100644 --- a/toolkit/components/reader/JSDOMParser.js +++ b/toolkit/components/reader/JSDOMParser.js @@ -315,6 +315,7 @@ } } getElems(this); + elems._isLiveNodeList = true; return elems; } @@ -503,17 +504,9 @@ }, setValue: function(newValue) { this._value = newValue; - delete this._decodedValue; }, - setDecodedValue: function(newValue) { - this._value = encodeHTML(newValue); - this._decodedValue = newValue; - }, - getDecodedValue: function() { - if (typeof this._decodedValue === "undefined") { - this._decodedValue = (this._value && decodeHTML(this._value)) || ""; - } - return this._decodedValue; + getEncodedValue: function() { + return encodeHTML(this._value); }, }; @@ -673,6 +666,14 @@ this.setAttribute("src", str); }, + get srcset() { + return this.getAttribute("srcset") || ""; + }, + + set srcset(str) { + this.setAttribute("srcset", str); + }, + get nodeName() { return this.tagName; }, @@ -689,7 +690,7 @@ for (var j = 0; j < child.attributes.length; j++) { var attr = child.attributes[j]; // the attribute value will be HTML escaped. - var val = attr.value; + var val = attr.getEncodedValue(); var quote = (val.indexOf('"') === -1 ? '"' : "'"); arr.push(" " + attr.name + "=" + quote + val + quote); } @@ -767,8 +768,9 @@ getAttribute: function (name) { for (var i = this.attributes.length; --i >= 0;) { var attr = this.attributes[i]; - if (attr.name === name) - return attr.getDecodedValue(); + if (attr.name === name) { + return attr.value; + } } return undefined; }, @@ -777,11 +779,11 @@ for (var i = this.attributes.length; --i >= 0;) { var attr = this.attributes[i]; if (attr.name === name) { - attr.setDecodedValue(value); + attr.setValue(value); return; } } - this.attributes.push(new Attribute(name, encodeHTML(value))); + this.attributes.push(new Attribute(name, value)); }, removeAttribute: function (name) { @@ -945,7 +947,7 @@ // Read the attribute value (and consume the matching quote) var value = this.readString(c); - node.attributes.push(new Attribute(name, value)); + node.attributes.push(new Attribute(name, decodeHTML(value))); return; }, diff --git a/toolkit/components/reader/Readability-readerable.js b/toolkit/components/reader/Readability-readerable.js index d0e1b8164a..839d9fbf74 100644 --- a/toolkit/components/reader/Readability-readerable.js +++ b/toolkit/components/reader/Readability-readerable.js @@ -31,13 +31,16 @@ var REGEXPS = { // NOTE: These two regular expressions are duplicated in // Readability.js. Please keep both copies in sync. - unlikelyCandidates: /-ad-|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, - okMaybeItsACandidate: /and|article|body|column|main|shadow/i, + unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, + okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i, }; function isNodeVisible(node) { - // Have to null-check node.style to deal with SVG and MathML nodes. - return (!node.style || node.style.display != "none") && !node.hasAttribute("hidden"); + // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes. + return (!node.style || node.style.display != "none") + && !node.hasAttribute("hidden") + //check for "fallback-image" so that wikimedia math images are displayed + && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1)); } /** diff --git a/toolkit/components/reader/Readability.js b/toolkit/components/reader/Readability.js index 69fb53f868..4a36898850 100644 --- a/toolkit/components/reader/Readability.js +++ b/toolkit/components/reader/Readability.js @@ -43,6 +43,7 @@ function Readability(doc, options) { options = options || {}; this._doc = doc; + this._docJSDOMParser = this._doc.firstChild.__JSDOMParser__; this._articleTitle = null; this._articleByline = null; this._articleDir = null; @@ -55,6 +56,7 @@ function Readability(doc, options) { this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES; this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD; this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(options.classesToPreserve || []); + this._keepClasses = !!options.keepClasses; // Start with all flags set this._flags = this.FLAG_STRIP_UNLIKELYS | @@ -121,20 +123,23 @@ Readability.prototype = { REGEXPS: { // NOTE: These two regular expressions are duplicated in // Readability-readerable.js. Please keep both copies in sync. - unlikelyCandidates: /-ad-|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, - okMaybeItsACandidate: /and|article|body|column|main|shadow/i, + unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, + okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i, positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i, - negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, + negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i, byline: /byline|author|dateline|writtenby|p-author/i, replaceFonts: /<(\/?)font[^>]*>/gi, normalize: /\s{2,}/g, videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i, + shareElements: /(\b|_)(share|sharedaddy)(\b|_)/i, nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i, prevLink: /(prev|earl|old|new|<|«)/i, whitespace: /^\s*$/, hasContent: /\S$/, + srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g, + b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i }, DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ], @@ -159,6 +164,15 @@ Readability.prototype = { // These are the classes that readability sets itself. CLASSES_TO_PRESERVE: [ "page" ], + // These are the list of HTML entities that need to be escaped. + HTML_ESCAPE_MAP: { + "lt": "<", + "gt": ">", + "amp": "&", + "quot": '"', + "apos": "'", + }, + /** * Run any post-process modifications to article content as necessary. * @@ -169,8 +183,10 @@ Readability.prototype = { // Readability cannot open relative uris so we convert them to absolute uris. this._fixRelativeUris(articleContent); - // Remove classes. - this._cleanClasses(articleContent); + if (!this._keepClasses) { + // Remove classes. + this._cleanClasses(articleContent); + } }, /** @@ -184,6 +200,10 @@ Readability.prototype = { * @return void */ _removeNodes: function(nodeList, filterFn) { + // Avoid ever operating on live node lists. + if (this._docJSDOMParser && nodeList._isLiveNodeList) { + throw new Error("Do not pass live node lists to _removeNodes"); + } for (var i = nodeList.length - 1; i >= 0; i--) { var node = nodeList[i]; var parentNode = node.parentNode; @@ -203,6 +223,10 @@ Readability.prototype = { * @return void */ _replaceNodeTags: function(nodeList, newTagName) { + // Avoid ever operating on live node lists. + if (this._docJSDOMParser && nodeList._isLiveNodeList) { + throw new Error("Do not pass live node lists to _replaceNodeTags"); + } for (var i = nodeList.length - 1; i >= 0; i--) { var node = nodeList[i]; this._setNodeTag(node, newTagName); @@ -322,6 +346,7 @@ Readability.prototype = { if (baseURI == documentURI && uri.charAt(0) == "#") { return uri; } + // Otherwise, resolve against base URI: try { return new URL(uri, baseURI).href; @@ -335,22 +360,50 @@ Readability.prototype = { this._forEachNode(links, function(link) { var href = link.getAttribute("href"); if (href) { - // Replace links with javascript: URIs with text content, since + // Remove links with javascript: URIs, since // they won't work after scripts have been removed from the page. if (href.indexOf("javascript:") === 0) { - var text = this._doc.createTextNode(link.textContent); - link.parentNode.replaceChild(text, link); + // if the link only contains simple text content, it can be converted to a text node + if (link.childNodes.length === 1 && link.childNodes[0].nodeType === this.TEXT_NODE) { + var text = this._doc.createTextNode(link.textContent); + link.parentNode.replaceChild(text, link); + } else { + // if the link has multiple children, they should all be preserved + var container = this._doc.createElement("span"); + while (link.childNodes.length > 0) { + container.appendChild(link.childNodes[0]); + } + link.parentNode.replaceChild(container, link); + } } else { link.setAttribute("href", toAbsoluteURI(href)); } } }); - var imgs = this._getAllNodesWithTag(articleContent, ["img"]); - this._forEachNode(imgs, function(img) { - var src = img.getAttribute("src"); + var medias = this._getAllNodesWithTag(articleContent, [ + "img", "picture", "figure", "video", "audio", "source" + ]); + + this._forEachNode(medias, function(media) { + var src = media.getAttribute("src"); + var poster = media.getAttribute("poster"); + var srcset = media.getAttribute("srcset"); + if (src) { - img.setAttribute("src", toAbsoluteURI(src)); + media.setAttribute("src", toAbsoluteURI(src)); + } + + if (poster) { + media.setAttribute("poster", toAbsoluteURI(poster)); + } + + if (srcset) { + var newSrcset = srcset.replace(this.REGEXPS.srcsetUrl, function(_, p1, p2, p3) { + return toAbsoluteURI(p1) + (p2 || "") + p3; + }); + + media.setAttribute("srcset", newSrcset); } }); }, @@ -444,13 +497,13 @@ Readability.prototype = { var doc = this._doc; // Remove all style tags in head - this._removeNodes(doc.getElementsByTagName("style")); + this._removeNodes(this._getAllNodesWithTag(doc, ["style"])); if (doc.body) { this._replaceBrs(doc.body); } - this._replaceNodeTags(doc.getElementsByTagName("font"), "SPAN"); + this._replaceNodeTags(this._getAllNodesWithTag(doc, ["font"]), "SPAN"); }, /** @@ -530,7 +583,7 @@ Readability.prototype = { _setNodeTag: function (node, tag) { this.log("_setNodeTag", node, tag); - if (node.__JSDOMParser__) { + if (this._docJSDOMParser) { node.localName = tag.toLowerCase(); node.tagName = tag.toUpperCase(); return node; @@ -545,7 +598,16 @@ Readability.prototype = { replacement.readability = node.readability; for (var i = 0; i < node.attributes.length; i++) { - replacement.setAttribute(node.attributes[i].name, node.attributes[i].value); + try { + replacement.setAttribute(node.attributes[i].name, node.attributes[i].value); + } catch (ex) { + /* it's possible for setAttribute() to throw if the attribute name + * isn't a valid XML Name. Such attributes can however be parsed from + * source in HTML docs, see https://github.com/whatwg/html/issues/4275, + * so we can hit them here and then throw. We don't care about such + * attributes so we ignore them. + */ + } } return replacement; }, @@ -565,6 +627,8 @@ Readability.prototype = { // visually linked to other content-ful elements (text, images, etc.). this._markDataTables(articleContent); + this._fixLazyImages(articleContent); + // Clean out junk from the article content this._cleanConditionally(articleContent, "form"); this._cleanConditionally(articleContent, "fieldset"); @@ -575,10 +639,15 @@ Readability.prototype = { this._clean(articleContent, "link"); this._clean(articleContent, "aside"); - // Clean out elements have "share" in their id/class combinations from final top candidates, + // Clean out elements with little content that have "share" in their id/class combinations from final top candidates, // which means we don't remove the top candidates even they have "share". - this._forEachNode(articleContent.children, function(topCandidate) { - this._cleanMatchedNodes(topCandidate, /share/); + + var shareElementThreshold = this.DEFAULT_CHAR_THRESHOLD; + + this._forEachNode(articleContent.children, function (topCandidate) { + this._cleanMatchedNodes(topCandidate, function (node, matchString) { + return this.REGEXPS.shareElements.test(matchString) && node.textContent.length < shareElementThreshold; + }); }); // If there is only one h2 and its text content substantially equals article title, @@ -614,7 +683,7 @@ Readability.prototype = { this._cleanConditionally(articleContent, "div"); // Remove extra paragraphs - this._removeNodes(articleContent.getElementsByTagName("p"), function (paragraph) { + this._removeNodes(this._getAllNodesWithTag(articleContent, ["p"]), function (paragraph) { var imgCount = paragraph.getElementsByTagName("img").length; var embedCount = paragraph.getElementsByTagName("embed").length; var objectCount = paragraph.getElementsByTagName("object").length; @@ -729,9 +798,10 @@ Readability.prototype = { if (node.getAttribute !== undefined) { var rel = node.getAttribute("rel"); + var itemprop = node.getAttribute("itemprop"); } - if ((rel === "author" || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) { + if ((rel === "author" || (itemprop && itemprop.indexOf("author") !== -1) || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) { this._articleByline = node.textContent.trim(); return true; } @@ -800,12 +870,19 @@ Readability.prototype = { if (stripUnlikelyCandidates) { if (this.REGEXPS.unlikelyCandidates.test(matchString) && !this.REGEXPS.okMaybeItsACandidate.test(matchString) && + !this._hasAncestorTag(node, "table") && node.tagName !== "BODY" && node.tagName !== "A") { this.log("Removing unlikely candidate - " + matchString); node = this._removeAndGetNext(node); continue; } + + if (node.getAttribute("role") == "complementary") { + this.log("Removing complementary content - " + matchString); + node = this._removeAndGetNext(node); + continue; + } } // Remove DIV, SECTION, and HEADER nodes without any content(e.g. text, image, video, or iframe). @@ -1199,6 +1276,26 @@ Readability.prototype = { return false; }, + /** + * Converts some of the common HTML entities in string to their corresponding characters. + * + * @param str {string} - a string to unescape. + * @return string without HTML entity. + */ + _unescapeHtmlEntities: function(str) { + if (!str) { + return str; + } + + var htmlEscapeMap = this.HTML_ESCAPE_MAP; + return str.replace(/&(quot|amp|apos|lt|gt);/g, function(_, tag) { + return htmlEscapeMap[tag]; + }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(_, hex, numStr) { + var num = parseInt(hex || numStr, hex ? 16 : 10); + return String.fromCharCode(num); + }); + }, + /** * Attempts to get excerpt and byline metadata for the article. * @@ -1220,6 +1317,9 @@ Readability.prototype = { var elementName = element.getAttribute("name"); var elementProperty = element.getAttribute("property"); var content = element.getAttribute("content"); + if (!content) { + return; + } var matches = null; var name = null; @@ -1276,21 +1376,123 @@ Readability.prototype = { // get site name metadata.siteName = values["og:site_name"]; + // in many sites the meta value is escaped with HTML entities, + // so here we need to unescape it + metadata.title = this._unescapeHtmlEntities(metadata.title); + metadata.byline = this._unescapeHtmlEntities(metadata.byline); + metadata.excerpt = this._unescapeHtmlEntities(metadata.excerpt); + metadata.siteName = this._unescapeHtmlEntities(metadata.siteName); + return metadata; }, + /** + * Check if node is image, or if node contains exactly only one image + * whether as a direct child or as its descendants. + * + * @param Element + **/ + _isSingleImage: function(node) { + if (node.tagName === "IMG") { + return true; + } + + if (node.children.length !== 1 || node.textContent.trim() !== "") { + return false; + } + + return this._isSingleImage(node.children[0]); + }, + + /** + * Find all