diff --git a/.travis.yml b/.travis.yml index c83427e85b..1bccfbf9d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,4 +37,4 @@ before_install: # whitelist branches: only: - - v3.10 + - v3 diff --git a/README.md b/README.md index d935f9eb82..3ae2159add 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cocos2d-x is: * Fast * Free * Easy to use - * Community Supported + * Community supported Git user attention ----------------------- @@ -110,7 +110,7 @@ Starting with Cocos2d-x v3.3, you can create Windows 8.1 Universal Apps (Windows Starting with Cocos2d-x v3.8 you can create Windows 10.0 UWP Apps (Windows Store and Windows Phone 10.0). Starting with Cocos2d-x v3.6 there will no longer be support for Windows Phone 8.0. -See more info on How to install and Create games on Windows RT (Windows and Windows Phone 8.1) at http://msopentech.github.io/cocos2d-x/ +See more info on how to install and create games on Windows RT (Windows and Windows Phone 8.1) at http://msopentech.github.io/cocos2d-x/ ### Build and run new project for web ### @@ -295,7 +295,7 @@ __cocos2d-x_root/build.__ Contributing to the Project -------------------------------- -Cocos2d-x is licensed under the [MIT License](https://opensource.org/licenses/MIT). We welcome paricipation! +Cocos2d-x is licensed under the [MIT License](https://opensource.org/licenses/MIT). We welcome participation! Did you find a bug? Do you have feature request? Do you want to merge a feature? diff --git a/cocos/2d/CCAction.h b/cocos/2d/CCAction.h index 89733af0b8..504cbb0534 100644 --- a/cocos/2d/CCAction.h +++ b/cocos/2d/CCAction.h @@ -309,7 +309,7 @@ private: * @brief Follow is an action that "follows" a node. * Eg: * @code - * layer->runAction(Follow::actionWithTarget(hero)); + * layer->runAction(Follow::create(hero)); * @endcode * Instead of using Camera as a "follower", use this action instead. * @since v0.99.2 diff --git a/cocos/2d/CCActionGrid.h b/cocos/2d/CCActionGrid.h index 5687acd344..65405b9e09 100644 --- a/cocos/2d/CCActionGrid.h +++ b/cocos/2d/CCActionGrid.h @@ -366,7 +366,9 @@ private: @brief StopGrid action. @warning Don't call this action if another grid action is active. Call if you want to remove the grid effect. Example: - Sequence::actions(Lens::action(...), StopGrid::action(...), nullptr); + @code + Sequence::create(Lens3D::create(...), StopGrid::create(), nullptr); + @endcode */ class CC_DLL StopGrid : public ActionInstant { diff --git a/cocos/2d/CCActionInterval.h b/cocos/2d/CCActionInterval.h index 93b2d79df2..c1ef255764 100644 --- a/cocos/2d/CCActionInterval.h +++ b/cocos/2d/CCActionInterval.h @@ -61,7 +61,10 @@ then running it again in Reverse mode. Example: -Action *pingPongAction = Sequence::actions(action, action->reverse(), nullptr); +@code +auto action = MoveBy::create(1.0f, Vec2::ONE); +auto pingPongAction = Sequence::create(action, action->reverse(), nullptr); +@endcode */ class CC_DLL ActionInterval : public FiniteTimeAction { diff --git a/cocos/2d/CCFontAtlasCache.cpp b/cocos/2d/CCFontAtlasCache.cpp index 2fdc1f98f0..d1eb933f24 100644 --- a/cocos/2d/CCFontAtlasCache.cpp +++ b/cocos/2d/CCFontAtlasCache.cpp @@ -266,7 +266,7 @@ void FontAtlasCache::unloadFontAtlasTTF(const std::string& fontFileName) auto item = _atlasMap.begin(); while (item != _atlasMap.end()) { - if (item->first.find(fontFileName) >= 0) + if (item->first.find(fontFileName) != std::string::npos) { CC_SAFE_RELEASE_NULL(item->second); item = _atlasMap.erase(item); diff --git a/cocos/2d/CCFontFNT.cpp b/cocos/2d/CCFontFNT.cpp index 3914a0ff70..6b331f0aa2 100644 --- a/cocos/2d/CCFontFNT.cpp +++ b/cocos/2d/CCFontFNT.cpp @@ -279,21 +279,37 @@ std::set* BMFontConfiguration::parseConfigFile(const std::string& CCASSERT((!data.isNull()), "BMFontConfiguration::parseConfigFile | Open file error."); if (memcmp("BMF", data.getBytes(), 3) == 0) { + // Handle fnt file of binary format std::set* ret = parseBinaryConfigFile(data.getBytes(), data.getSize(), controlFile); return ret; } - auto contents = (const char*)data.getBytes(); - if (contents[0] == 0) + if (data.getBytes()[0] == 0) { CCLOG("cocos2d: Error parsing FNTfile %s", controlFile.c_str()); return nullptr; } - + + // Handle fnt file of string format, allocate one extra byte '\0' at the end since c string needs it. + // 'strchr' finds a char until it gets a '\0', if 'contents' self doesn't end with '\0', + // 'strchr' will search '\n' out of 'contents' 's buffer size, it will trigger potential and random crashes since + // lineLength may bigger than 512 and 'memcpy(line, contents + parseCount, lineLength);' will cause stack buffer overflow. + // Please note that 'contents' needs to be freed before this function returns. + char* contents = (char*)malloc(data.getSize() + 1); + if (contents == nullptr) + { + CCLOGERROR("BMFontConfiguration::parseConfigFile, out of memory!"); + return nullptr; + } + + memcpy(contents, data.getBytes(), data.getSize()); + // Ensure the last byte is '\0' + contents[data.getSize()] = '\0'; + std::set *validCharsString = new (std::nothrow) std::set(); - auto contentsLen = data.getSize(); - char line[512]; + auto contentsLen = strlen(contents); + char line[512] = {0}; auto next = strchr(contents, '\n'); auto base = contents; @@ -357,6 +373,8 @@ std::set* BMFontConfiguration::parseConfigFile(const std::string& } } + CC_SAFE_FREE(contents); + return validCharsString; } diff --git a/cocos/2d/CCFontFreeType.cpp b/cocos/2d/CCFontFreeType.cpp index e8e0bb2daa..581c74acee 100644 --- a/cocos/2d/CCFontFreeType.cpp +++ b/cocos/2d/CCFontFreeType.cpp @@ -141,7 +141,7 @@ bool FontFreeType::createFontObject(const std::string &fontName, float fontSize) if (FT_New_Memory_Face(getFTLibrary(), s_cacheFontData[fontName].data.getBytes(), s_cacheFontData[fontName].data.getSize(), 0, &face )) return false; - + if (FT_Select_Charmap(face, FT_ENCODING_UNICODE)) { int foundIndex = -1; @@ -274,6 +274,14 @@ int FontFreeType::getFontAscender() const return (static_cast(_fontRef->size->metrics.ascender >> 6)); } +const char* FontFreeType::getFontFamily() const +{ + if (!_fontRef) + return nullptr; + + return _fontRef->family_name; +} + unsigned char* FontFreeType::getGlyphBitmap(unsigned short theChar, long &outWidth, long &outHeight, Rect &outRect,int &xAdvance) { bool invalidChar = true; @@ -639,7 +647,7 @@ void FontFreeType::releaseFont(const std::string &fontName) auto item = s_cacheFontData.begin(); while (s_cacheFontData.end() != item) { - if (item->first.find(fontName) >= 0) + if (item->first.find(fontName) != std::string::npos) item = s_cacheFontData.erase(item); else item++; diff --git a/cocos/2d/CCFontFreeType.h b/cocos/2d/CCFontFreeType.h index 51600e1b30..b3406181c8 100644 --- a/cocos/2d/CCFontFreeType.h +++ b/cocos/2d/CCFontFreeType.h @@ -71,6 +71,7 @@ public: unsigned char* getGlyphBitmap(unsigned short theChar, long &outWidth, long &outHeight, Rect &outRect,int &xAdvance); int getFontAscender() const; + const char* getFontFamily() const; virtual FontAtlas* createFontAtlas() override; virtual int getFontMaxHeight() const override { return _lineHeight; } diff --git a/cocos/2d/CCLabel.cpp b/cocos/2d/CCLabel.cpp index 8220a75ec9..b9d3db10b5 100644 --- a/cocos/2d/CCLabel.cpp +++ b/cocos/2d/CCLabel.cpp @@ -24,6 +24,9 @@ ****************************************************************************/ #include "2d/CCLabel.h" + +#include + #include "2d/CCFont.h" #include "2d/CCFontAtlasCache.h" #include "2d/CCFontAtlas.h" @@ -43,6 +46,7 @@ NS_CC_BEGIN +static const int UNDERLINE_NODE_TAG = 0xaabbccdd; /** * LabelLetter used to update the quad in texture atlas without SpriteBatchNode. */ @@ -207,7 +211,6 @@ Label* Label::createWithSystemFont(const std::string& text, const std::string& f return ret; } - delete ret; return nullptr; } @@ -381,6 +384,9 @@ Label::Label(TextHAlignment hAlignment /* = TextHAlignment::LEFT */, , _fontAtlas(nullptr) , _reusedLetter(nullptr) , _horizontalKernings(nullptr) +, _boldEnabled(false) +, _underlineNode(nullptr) +, _strikethroughEnabled(false) { setAnchorPoint(Vec2::ANCHOR_MIDDLE); reset(); @@ -510,7 +516,14 @@ void Label::reset() _bmfontScale = 1.0f; _overflow = Overflow::NONE; _originalFontSize = 0.0f; - + _boldEnabled = false; + if (_underlineNode) + { + removeChild(_underlineNode); + _underlineNode = nullptr; + } + _strikethroughEnabled = false; + setRotationSkewX(0); // reverse italics } void Label::updateShaderProgram() @@ -956,6 +969,16 @@ bool Label::setTTFConfigInternal(const TTFConfig& ttfConfig) _currLabelEffect = LabelEffect::NORMAL; updateShaderProgram(); } + + if (_fontConfig.italics) + this->enableItalics(); + if (_fontConfig.bold) + this->enableBold(); + if (_fontConfig.underline) + this->enableUnderline(); + if (_fontConfig.strikethrough) + this->enableStrikethrough(); + return true; } @@ -1096,55 +1119,113 @@ void Label::enableShadow(const Color4B& shadowColor /* = Color4B::BLACK */,const } } +void Label::enableItalics() +{ + setRotationSkewX(12); +} + +void Label::enableBold() +{ + if (!_boldEnabled) + { + // bold is implemented with outline + enableShadow(Color4B::WHITE, Size(0.9,0), 0); + // add one to kerning + setAdditionalKerning(_additionalKerning+1); + _boldEnabled = true; + } +} + +void Label::enableUnderline() +{ + // remove it, just in case to prevent adding two or more + if (!_underlineNode) + { + _underlineNode = DrawNode::create(); + addChild(_underlineNode, 100000); + _contentDirty = true; + } +} + +void Label::enableStrikethrough() +{ + if (!_strikethroughEnabled) + { + enableUnderline(); + _strikethroughEnabled = true; + } +} + void Label::disableEffect() { - disableEffect(LabelEffect::GLOW); - disableEffect(LabelEffect::OUTLINE); - disableEffect(LabelEffect::SHADOW); + disableEffect(LabelEffect::ALL); } void Label::disableEffect(LabelEffect effect) { switch (effect) { - case cocos2d::LabelEffect::NORMAL: - break; - case cocos2d::LabelEffect::OUTLINE: - if (_currLabelEffect == LabelEffect::OUTLINE) - { - if (_currentLabelType == LabelType::TTF) + case cocos2d::LabelEffect::NORMAL: + break; + case cocos2d::LabelEffect::OUTLINE: + if (_currLabelEffect == LabelEffect::OUTLINE) { - _fontConfig.outlineSize = 0; - setTTFConfig(_fontConfig); + if (_currentLabelType == LabelType::TTF) + { + _fontConfig.outlineSize = 0; + setTTFConfig(_fontConfig); + } + _currLabelEffect = LabelEffect::NORMAL; + _contentDirty = true; } - - _currLabelEffect = LabelEffect::NORMAL; - _contentDirty = true; - } - break; - case cocos2d::LabelEffect::SHADOW: - if (_shadowEnabled) - { - _shadowEnabled = false; - CC_SAFE_RELEASE_NULL(_shadowNode); - } - break; - case cocos2d::LabelEffect::GLOW: - if (_currLabelEffect == LabelEffect::GLOW) - { - _currLabelEffect = LabelEffect::NORMAL; - updateShaderProgram(); - } - break; - case LabelEffect::ALL: + break; + case cocos2d::LabelEffect::SHADOW: + if (_shadowEnabled) + { + _shadowEnabled = false; + CC_SAFE_RELEASE_NULL(_shadowNode); + updateShaderProgram(); + } + break; + case cocos2d::LabelEffect::GLOW: + if (_currLabelEffect == LabelEffect::GLOW) + { + _currLabelEffect = LabelEffect::NORMAL; + updateShaderProgram(); + } + break; + case cocos2d::LabelEffect::ITALICS: + setRotationSkewX(0); + break; + case cocos2d::LabelEffect::BOLD: + _boldEnabled = false; + _additionalKerning -= 1; + disableEffect(LabelEffect::SHADOW); + break; + case cocos2d::LabelEffect::UNDERLINE: + if (_underlineNode) { + removeChild(_underlineNode); + _underlineNode = nullptr; + } + break; + case cocos2d::LabelEffect::STRIKETHROUGH: + _strikethroughEnabled = false; + // since it is based on underline, disable it as well + disableEffect(LabelEffect::UNDERLINE); + break; + case LabelEffect::ALL: { disableEffect(LabelEffect::SHADOW); disableEffect(LabelEffect::GLOW); disableEffect(LabelEffect::OUTLINE); + disableEffect(LabelEffect::ITALICS); + disableEffect(LabelEffect::BOLD); + disableEffect(LabelEffect::UNDERLINE); + disableEffect(LabelEffect::STRIKETHROUGH); } - break; - default: - break; + break; + default: + break; } } @@ -1299,6 +1380,42 @@ void Label::updateContent() createShadowSpriteForSystemFont(fontDef); } } + + if (_underlineNode) + { + _underlineNode->clear(); + + if (_numberOfLines) + { + const float charheight = (_textDesiredHeight / _numberOfLines); + _underlineNode->setLineWidth(charheight/6); + + // atlas font + for (int i=0; i<_numberOfLines; ++i) + { + float offsety = 0; + if (_strikethroughEnabled) + offsety += charheight / 2; + // FIXME: Might not work with different vertical alignments + float y = (_numberOfLines - i - 1) * charheight + offsety; + _underlineNode->drawLine(Vec2(_linesOffsetX[i],y), Vec2(_linesWidth[i] + _linesOffsetX[i],y), _textColorF); + } + } + else if (_textSprite) + { + // system font + float y = 0; + const auto spriteSize = _textSprite->getContentSize(); + _underlineNode->setLineWidth(spriteSize.height/6); + + if (_strikethroughEnabled) + // FIXME: system fonts don't report the height of the font correctly. only the size of the texture, which is POT + y += spriteSize.height / 2; + // FIXME: Might not work with different vertical alignments + _underlineNode->drawLine(Vec2(0,y), Vec2(spriteSize.width,y), Color4F(_textSprite->getDisplayedColor())); + } + } + if(updateFinished){ _contentDirty = false; } @@ -1327,16 +1444,16 @@ float Label::getBMFontSize()const return _bmFontSize; } -void Label::onDrawShadow(GLProgram* glProgram) +void Label::onDrawShadow(GLProgram* glProgram, const Color4F& shadowColor) { if (_currentLabelType == LabelType::TTF) { glProgram->setUniformLocationWith4f(_uniformTextColor, - _shadowColor4F.r, _shadowColor4F.g, _shadowColor4F.b, _shadowColor4F.a); + shadowColor.r, shadowColor.g, shadowColor.b, shadowColor.a); if (_currLabelEffect == LabelEffect::OUTLINE || _currLabelEffect == LabelEffect::GLOW) { glProgram->setUniformLocationWith4f(_uniformEffectColor, - _shadowColor4F.r, _shadowColor4F.g, _shadowColor4F.b, _shadowColor4F.a); + shadowColor.r, shadowColor.g, shadowColor.b, shadowColor.a); } glProgram->setUniformsForBuiltins(_shadowTransform); @@ -1353,8 +1470,8 @@ void Label::onDrawShadow(GLProgram* glProgram) { Color3B oldColor = _realColor; GLubyte oldOPacity = _displayedOpacity; - _displayedOpacity = _shadowOpacity; - setColor(_shadowColor3B); + _displayedOpacity = shadowColor.a * 255; + setColor(Color3B(shadowColor)); glProgram->setUniformsForBuiltins(_shadowTransform); for (auto&& it : _letters) @@ -1379,7 +1496,10 @@ void Label::onDraw(const Mat4& transform, bool transformUpdated) if (_shadowEnabled) { - onDrawShadow(glprogram); + if (_boldEnabled) + onDrawShadow(glprogram, _textColorF); + else + onDrawShadow(glprogram, _shadowColor4F); } glprogram->setUniformsForBuiltins(transform); @@ -1672,18 +1792,21 @@ float Label::getLineSpacing() const void Label::setAdditionalKerning(float space) { - CCASSERT(_currentLabelType != LabelType::STRING_TEXTURE, "Not supported system font!"); - if (_additionalKerning != space) + + if (_currentLabelType != LabelType::STRING_TEXTURE) { - _additionalKerning = space; - _contentDirty = true; + if (_additionalKerning != space) + { + _additionalKerning = space; + _contentDirty = true; + } } + else + CCLOG("Label::setAdditionalKerning not supported on LabelType::STRING_TEXTURE"); } float Label::getAdditionalKerning() const { - CCASSERT(_currentLabelType != LabelType::STRING_TEXTURE, "Not supported system font!"); - return _additionalKerning; } @@ -1752,6 +1875,9 @@ void Label::updateDisplayedColor(const Color3B& parentColor) { _shadowNode->updateDisplayedColor(_displayedColor); } + + if (_underlineNode) + _contentDirty = true; } for (auto&& it : _letters) diff --git a/cocos/2d/CCLabel.h b/cocos/2d/CCLabel.h index 480e22761d..bce5bc270f 100644 --- a/cocos/2d/CCLabel.h +++ b/cocos/2d/CCLabel.h @@ -55,14 +55,24 @@ typedef struct _ttfConfig bool distanceFieldEnabled; int outlineSize; + bool italics; + bool bold; + bool underline; + bool strikethrough; + _ttfConfig(const std::string& filePath = "",float size = 12, const GlyphCollection& glyphCollection = GlyphCollection::DYNAMIC, - const char *customGlyphCollection = nullptr, bool useDistanceField = false, int outline = 0) + const char *customGlyphCollection = nullptr, bool useDistanceField = false, int outline = 0, + bool useItalics = false, bool useBold = false, bool useUnderline = false, bool useStrikethrough = false) : fontFilePath(filePath) , fontSize(size) , glyphs(glyphCollection) , customGlyphs(customGlyphCollection) , distanceFieldEnabled(useDistanceField) , outlineSize(outline) + , italics(useItalics) + , bold(useBold) + , underline(useUnderline) + , strikethrough(useStrikethrough) { if(outline > 0) { @@ -336,6 +346,27 @@ public: */ virtual void enableGlow(const Color4B& glowColor); + /** + * Enable italics rendering + */ + void enableItalics(); + + /** + * Enable bold rendering + */ + void enableBold(); + + /** + * Enable underline + */ + void enableUnderline(); + + /** + * Enables strikethrough. + * Underline and Strikethrough cannot be enabled at the same time. + * Strikethough is like an underline but at the middle of the glyph + */ + void enableStrikethrough(); /** * Disable all effect to Label. * @warning Please use disableEffect(LabelEffect::ALL) instead of this API. @@ -605,7 +636,7 @@ protected: void computeStringNumLines(); void onDraw(const Mat4& transform, bool transformUpdated); - void onDrawShadow(GLProgram* glProgram); + void onDrawShadow(GLProgram* glProgram, const Color4F& shadowColor); void drawSelf(bool visibleByCamera, Renderer* renderer, uint32_t flags); bool multilineTextWrapByChar(); @@ -735,6 +766,11 @@ protected: float _bmfontScale; Overflow _overflow; float _originalFontSize; + + bool _boldEnabled; + DrawNode* _underlineNode; + bool _strikethroughEnabled; + private: CC_DISALLOW_COPY_AND_ASSIGN(Label); }; diff --git a/cocos/2d/CCSpriteFrame.h b/cocos/2d/CCSpriteFrame.h index 8e33276418..ae443b1ff3 100644 --- a/cocos/2d/CCSpriteFrame.h +++ b/cocos/2d/CCSpriteFrame.h @@ -50,8 +50,10 @@ class Texture2D; You can modify the frame of a Sprite by doing: - SpriteFrame *frame = SpriteFrame::frameWithTexture(texture, rect, offset); - sprite->setDisplayFrame(frame); + @code + SpriteFrame* frame = SpriteFrame::createWithTexture(texture, rect); + sprite->setSpriteFrame(frame); + @endcode */ class CC_DLL SpriteFrame : public Ref, public Clonable { diff --git a/cocos/2d/libcocos2d.vcxproj b/cocos/2d/libcocos2d.vcxproj index edbe75585f..7fb8d898af 100644 --- a/cocos/2d/libcocos2d.vcxproj +++ b/cocos/2d/libcocos2d.vcxproj @@ -117,7 +117,7 @@ xcopy /Y /Q "$(ProjectDir)..\..\external\chipmunk\prebuilt\win32\debug-lib\*.*" $(TargetDir)$(TargetName).lib MachineX86 cocos2d.def - sqlite3.lib;libcurl_imp.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;libbullet.lib;%(AdditionalDependencies) + sqlite3.lib;libcurl_imp.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;libbullet.lib;version.lib;%(AdditionalDependencies) @@ -164,7 +164,7 @@ xcopy /Y /Q "$(ProjectDir)..\..\external\chipmunk\prebuilt\win32\release-lib\*.* - sqlite3.lib;libcurl_imp.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;libbullet.lib;%(AdditionalDependencies) + sqlite3.lib;libcurl_imp.lib;websockets.lib;libmpg123.lib;libogg.lib;libvorbis.lib;libvorbisfile.lib;OpenAL32.lib;libbullet.lib;version.lib;%(AdditionalDependencies) $(OutDir)$(ProjectName).dll $(OutDir);%(AdditionalLibraryDirectories) LIBCMTD.lib;%(IgnoreSpecificDefaultLibraries) diff --git a/cocos/3d/CCBundle3D.cpp b/cocos/3d/CCBundle3D.cpp index 762f548a5e..f248683c72 100644 --- a/cocos/3d/CCBundle3D.cpp +++ b/cocos/3d/CCBundle3D.cpp @@ -162,12 +162,12 @@ void Bundle3D::clear() { if (_isBinary) { - CC_SAFE_DELETE(_binaryBuffer); + _binaryBuffer.clear(); CC_SAFE_DELETE_ARRAY(_references); } else { - CC_SAFE_DELETE_ARRAY(_jsonBuffer); + _jsonBuffer.clear(); } } @@ -1038,14 +1038,9 @@ bool Bundle3D::loadJson(const std::string& path) { clear(); - Data data = FileUtils::getInstance()->getDataFromFile(path); - ssize_t size = data.getSize(); + _jsonBuffer = FileUtils::getInstance()->getStringFromFile(path); - // json need null-terminated string. - _jsonBuffer = new char[size + 1]; - memcpy(_jsonBuffer, data.getBytes(), size); - _jsonBuffer[size] = '\0'; - if (_jsonReader.ParseInsitu<0>(_jsonBuffer).HasParseError()) + if (_jsonReader.ParseInsitu<0>((char*)_jsonBuffer.c_str()).HasParseError()) { clear(); CCLOG("Parse json failed in Bundle3D::loadJson function"); @@ -1067,10 +1062,9 @@ bool Bundle3D::loadBinary(const std::string& path) clear(); // get file data - CC_SAFE_DELETE(_binaryBuffer); - _binaryBuffer = new (std::nothrow) Data(); - *_binaryBuffer = FileUtils::getInstance()->getDataFromFile(path); - if (_binaryBuffer->isNull()) + _binaryBuffer.clear(); + _binaryBuffer = FileUtils::getInstance()->getDataFromFile(path); + if (_binaryBuffer.isNull()) { clear(); CCLOG("warning: Failed to read file: %s", path.c_str()); @@ -1078,7 +1072,7 @@ bool Bundle3D::loadBinary(const std::string& path) } // Initialise bundle reader - _binaryReader.init( (char*)_binaryBuffer->getBytes(), _binaryBuffer->getSize() ); + _binaryReader.init( (char*)_binaryBuffer.getBytes(), _binaryBuffer.getSize() ); // Read identifier info char identifier[] = { 'C', '3', 'B', '\0'}; @@ -2199,8 +2193,6 @@ Bundle3D::Bundle3D() : _modelPath(""), _path(""), _version(""), -_jsonBuffer(nullptr), -_binaryBuffer(nullptr), _referenceCount(0), _references(nullptr), _isBinary(false) diff --git a/cocos/3d/CCBundle3D.h b/cocos/3d/CCBundle3D.h index 60d681931a..8ef06703c0 100644 --- a/cocos/3d/CCBundle3D.h +++ b/cocos/3d/CCBundle3D.h @@ -25,6 +25,7 @@ #ifndef __CCBUNDLE3D_H__ #define __CCBUNDLE3D_H__ +#include "base/CCData.h" #include "3d/CCBundle3DData.h" #include "3d/CCBundleReader.h" #include "json/document.h" @@ -37,7 +38,6 @@ NS_CC_BEGIN */ class Animation3D; -class Data; /** * @brief Defines a bundle file that contains a collection of assets. Mesh, Material, MeshSkin, Animation @@ -177,11 +177,11 @@ protected: std::string _version;// the c3b or c3t version // for json reading - char* _jsonBuffer; + std::string _jsonBuffer; rapidjson::Document _jsonReader; // for binary reading - Data* _binaryBuffer; + Data _binaryBuffer; BundleReader _binaryReader; unsigned int _referenceCount; Reference* _references; diff --git a/cocos/audio/android/AudioEngine-inl.cpp b/cocos/audio/android/AudioEngine-inl.cpp index e41782eb35..18a391f179 100644 --- a/cocos/audio/android/AudioEngine-inl.cpp +++ b/cocos/audio/android/AudioEngine-inl.cpp @@ -38,7 +38,6 @@ #include "base/CCDirector.h" #include "base/CCScheduler.h" #include "platform/android/CCFileUtils-android.h" -#include "platform/android/jni/CocosPlayClient.h" using namespace cocos2d; using namespace cocos2d::experimental; @@ -249,14 +248,12 @@ int AudioEngineImpl::play2d(const std::string &filePath ,bool loop ,float volume auto& player = _audioPlayers[currentAudioID]; auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); - cocosplay::updateAssets(fullPath); auto initPlayer = player.init(_engineEngine, _outputMixObject, fullPath, volume, loop); if (!initPlayer){ _audioPlayers.erase(currentAudioID); log("%s,%d message:create player for %s fail", __func__, __LINE__, filePath.c_str()); break; } - cocosplay::notifyFileLoaded(fullPath); audioId = currentAudioID++; player._audioID = audioId; diff --git a/cocos/base/CCVector.h b/cocos/base/CCVector.h index 27967372b5..a119764c5a 100644 --- a/cocos/base/CCVector.h +++ b/cocos/base/CCVector.h @@ -300,7 +300,7 @@ public: * @param other The vector to be compared. * @return True if two vectors are equal, false if not. */ - bool equals(const Vector &other) + bool equals(const Vector &other) const { ssize_t s = this->size(); if (s != other.size()) diff --git a/cocos/base/ccTypes.h b/cocos/base/ccTypes.h index 9152eefb29..d4382cc894 100644 --- a/cocos/base/ccTypes.h +++ b/cocos/base/ccTypes.h @@ -63,7 +63,7 @@ struct CC_DLL Color3B bool operator!=(const Color4B& right) const; bool operator!=(const Color4F& right) const; - bool equals(const Color3B& other) + bool equals(const Color3B& other) const { return (*this == other); } @@ -144,7 +144,7 @@ struct CC_DLL Color4F bool operator!=(const Color3B& right) const; bool operator!=(const Color4B& right) const; - bool equals(const Color4F &other) + bool equals(const Color4F &other) const { return (*this == other); } @@ -570,14 +570,19 @@ public: }; /** - * @brief Possible LabelEffect used by Label. + * @brief Effects used by `Label` * */ enum class LabelEffect { + // FIXME: Covert them to bitwise. More than one effect should be supported NORMAL, OUTLINE, SHADOW, GLOW, + ITALICS, + BOLD, + UNDERLINE, + STRIKETHROUGH, ALL }; diff --git a/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.cpp b/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.cpp index 51d29f88d5..f1cc5a99a7 100644 --- a/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.cpp +++ b/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.cpp @@ -164,6 +164,16 @@ ActionTimeline* ActionTimelineCache::createActionFromJson(const std::string& fil return action->clone(); } +ActionTimeline* ActionTimelineCache::createActionFromContent(const std::string& fileName, const std::string& content) +{ + ActionTimeline* action = _animationActions.at(fileName); + if (action == nullptr) + { + action = loadAnimationActionWithContent(fileName, content); + } + return action->clone(); +} + ActionTimeline* ActionTimelineCache::loadAnimationActionWithFile(const std::string& fileName) { // Read content from file @@ -403,7 +413,7 @@ Frame* ActionTimelineCache::loadZOrderFrame(const rapidjson::Value& json) return frame; } - + ActionTimeline* ActionTimelineCache::createActionWithFlatBuffersFile(const std::string &fileName) { ActionTimeline* action = _animationActions.at(fileName); @@ -414,6 +424,16 @@ ActionTimeline* ActionTimelineCache::createActionWithFlatBuffersFile(const std:: return action->clone(); } +ActionTimeline* ActionTimelineCache::createActionWithDataBuffer(Data data, const std::string &fileName) +{ + ActionTimeline* action = _animationActions.at(fileName); + if (action == NULL) + { + action = loadAnimationWithDataBuffer(data, fileName); + } + return action->clone(); +} + ActionTimeline* ActionTimelineCache::loadAnimationActionWithFlatBuffersFile(const std::string &fileName) { // if already exists an action with filename, then return this action @@ -434,7 +454,7 @@ ActionTimeline* ActionTimelineCache::loadAnimationActionWithFlatBuffersFile(cons return action; } -ActionTimeline* ActionTimelineCache::loadAnimationWithDataBuffer(const cocos2d::Data data, const std::string fileName) +ActionTimeline* ActionTimelineCache::loadAnimationWithDataBuffer(const cocos2d::Data& data, const std::string& fileName) { // if already exists an action with filename, then return this action ActionTimeline* action = _animationActions.at(fileName); @@ -453,7 +473,7 @@ ActionTimeline* ActionTimelineCache::loadAnimationWithDataBuffer(const cocos2d:: return action; } -inline ActionTimeline* ActionTimelineCache::createActionWithDataBuffer(const cocos2d::Data data) +ActionTimeline* ActionTimelineCache::createActionWithDataBuffer(const cocos2d::Data& data) { auto csparsebinary = GetCSParseBinary(data.getBytes()); diff --git a/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.h b/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.h index b4bd6a299a..2329a75081 100644 --- a/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.h +++ b/cocos/editor-support/cocostudio/ActionTimeline/CCActionTimelineCache.h @@ -78,13 +78,16 @@ public: /** Clone a action with the specified name from the container. */ ActionTimeline* createActionFromJson(const std::string& fileName); + ActionTimeline* createActionFromContent(const std::string& fileName, const std::string& content); ActionTimeline* loadAnimationActionWithFile(const std::string& fileName); ActionTimeline* loadAnimationActionWithContent(const std::string&fileName, const std::string& content); ActionTimeline* createActionWithFlatBuffersFile(const std::string& fileName); + ActionTimeline* createActionWithDataBuffer(cocos2d::Data data, const std::string &fileName); + ActionTimeline* loadAnimationActionWithFlatBuffersFile(const std::string& fileName); - ActionTimeline* loadAnimationWithDataBuffer(const cocos2d::Data data, const std::string fileName); + ActionTimeline* loadAnimationWithDataBuffer(const cocos2d::Data& data, const std::string& fileName); ActionTimeline* createActionWithFlatBuffersForSimulator(const std::string& fileName); @@ -122,7 +125,7 @@ protected: Frame* loadBlendFrameWithFlatBuffers (const flatbuffers::BlendFrame* flatbuffers); void loadEasingDataWithFlatBuffers(Frame* frame, const flatbuffers::EasingData* flatbuffers); - inline ActionTimeline* createActionWithDataBuffer(const cocos2d::Data data); + inline ActionTimeline* createActionWithDataBuffer(const cocos2d::Data& data); protected: typedef std::function FrameCreateFunc; diff --git a/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.cpp b/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.cpp index de6ab1fd0d..006690722d 100644 --- a/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.cpp +++ b/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.cpp @@ -348,7 +348,7 @@ ActionTimeline* CSLoader::createTimeline(const std::string &filename) return nullptr; } -ActionTimeline* CSLoader::createTimeline(const Data data, const std::string& filename) +ActionTimeline* CSLoader::createTimeline(const Data& data, const std::string& filename) { std::string suffix = getExtentionName(filename); @@ -356,12 +356,12 @@ ActionTimeline* CSLoader::createTimeline(const Data data, const std::string& fil if (suffix == "csb") { - return cache->loadAnimationWithDataBuffer(data, filename); + return cache->createActionWithDataBuffer(data, filename); } else if (suffix == "json" || suffix == "ExportJson") { std::string content((char *)data.getBytes(), data.getSize()); - return cache->loadAnimationActionWithContent(filename, content); + return cache->createActionFromContent(filename, content); } return nullptr; @@ -853,12 +853,12 @@ Component* CSLoader::loadComAudio(const rapidjson::Value &json) return audio; } -cocos2d::Node* CSLoader::createNode(const Data data) +cocos2d::Node* CSLoader::createNode(const Data& data) { return createNode(data, nullptr); } -Node * CSLoader::createNode(const Data data, const ccNodeLoadCallback &callback) +Node * CSLoader::createNode(const Data& data, const ccNodeLoadCallback &callback) { CSLoader * loader = CSLoader::getInstance(); Node * node = nullptr; @@ -1016,7 +1016,7 @@ Node* CSLoader::nodeWithFlatBuffers(const flatbuffers::NodeTree *nodetree, const { Data buf = FileUtils::getInstance()->getDataFromFile(filePath); node = createNode(buf, callback); - action = timeline::ActionTimelineCache::getInstance()->loadAnimationWithDataBuffer(buf, filePath); + action = createTimeline(buf, filePath); } else { diff --git a/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.h b/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.h index bb2cfe516b..3f217ef212 100644 --- a/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.h +++ b/cocos/editor-support/cocostudio/ActionTimeline/CSLoader.h @@ -79,13 +79,13 @@ public: static cocos2d::Node* createNode(const std::string& filename); static cocos2d::Node* createNode(const std::string& filename, const ccNodeLoadCallback& callback); - static cocos2d::Node* createNode(const Data data); - static cocos2d::Node* createNode(const Data data, const ccNodeLoadCallback &callback); + static cocos2d::Node* createNode(const Data& data); + static cocos2d::Node* createNode(const Data& data, const ccNodeLoadCallback &callback); static cocos2d::Node* createNodeWithVisibleSize(const std::string& filename); static cocos2d::Node* createNodeWithVisibleSize(const std::string& filename, const ccNodeLoadCallback& callback); static cocostudio::timeline::ActionTimeline* createTimeline(const std::string& filename); - static cocostudio::timeline::ActionTimeline* createTimeline(const Data data, const std::string& filename); + static cocostudio::timeline::ActionTimeline* createTimeline(const Data& data, const std::string& filename); /* static cocostudio::timeline::ActionTimelineNode* createActionTimelineNode(const std::string& filename); diff --git a/cocos/editor-support/cocostudio/CSParseBinary_generated.h b/cocos/editor-support/cocostudio/CSParseBinary_generated.h index db61622459..cbc234db89 100644 --- a/cocos/editor-support/cocostudio/CSParseBinary_generated.h +++ b/cocos/editor-support/cocostudio/CSParseBinary_generated.h @@ -1497,6 +1497,9 @@ struct ScrollViewOptions : private flatbuffers::Table { const FlatSize *innerSize() const { return GetStruct(28); } int32_t direction() const { return GetField(30, 0); } uint8_t bounceEnabled() const { return GetField(32, 0); } + uint8_t scrollbarEnabeld() const { return GetField(34, 1); } + uint8_t scrollbarAutoHide() const { return GetField(36, 1); } + float scrollbarAutoHideTime() const { return GetField(38, 0.2); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, 4 /* widgetOptions */) && @@ -1516,6 +1519,9 @@ struct ScrollViewOptions : private flatbuffers::Table { VerifyField(verifier, 28 /* innerSize */) && VerifyField(verifier, 30 /* direction */) && VerifyField(verifier, 32 /* bounceEnabled */) && + VerifyField(verifier, 34 /* scrollbarEnabeld */) && + VerifyField(verifier, 36 /* scrollbarAutoHide */) && + VerifyField(verifier, 38 /* scrollbarAutoHideTime */) && verifier.EndTable(); } }; @@ -1538,10 +1544,13 @@ struct ScrollViewOptionsBuilder { void add_innerSize(const FlatSize *innerSize) { fbb_.AddStruct(28, innerSize); } void add_direction(int32_t direction) { fbb_.AddElement(30, direction, 0); } void add_bounceEnabled(uint8_t bounceEnabled) { fbb_.AddElement(32, bounceEnabled, 0); } + void add_scrollbarEnabeld(uint8_t scrollbarEnabeld) { fbb_.AddElement(34, scrollbarEnabeld, 1); } + void add_scrollbarAutoHide(uint8_t scrollbarAutoHide) { fbb_.AddElement(36, scrollbarAutoHide, 1); } + void add_scrollbarAutoHideTime(float scrollbarAutoHideTime) { fbb_.AddElement(38, scrollbarAutoHideTime, 0.2); } ScrollViewOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } ScrollViewOptionsBuilder &operator=(const ScrollViewOptionsBuilder &); flatbuffers::Offset Finish() { - auto o = flatbuffers::Offset(fbb_.EndTable(start_, 15)); + auto o = flatbuffers::Offset(fbb_.EndTable(start_, 18)); return o; } }; @@ -1561,8 +1570,12 @@ inline flatbuffers::Offset CreateScrollViewOptions(flatbuffer uint8_t backGroundScale9Enabled = 0, const FlatSize *innerSize = 0, int32_t direction = 0, - uint8_t bounceEnabled = 0) { + uint8_t bounceEnabled = 0, + uint8_t scrollbarEnabeld = 1, + uint8_t scrollbarAutoHide = 1, + float scrollbarAutoHideTime = 0.2) { ScrollViewOptionsBuilder builder_(_fbb); + builder_.add_scrollbarAutoHideTime(scrollbarAutoHideTime); builder_.add_direction(direction); builder_.add_innerSize(innerSize); builder_.add_scale9Size(scale9Size); @@ -1574,6 +1587,8 @@ inline flatbuffers::Offset CreateScrollViewOptions(flatbuffer builder_.add_bgColor(bgColor); builder_.add_backGroundImageData(backGroundImageData); builder_.add_widgetOptions(widgetOptions); + builder_.add_scrollbarAutoHide(scrollbarAutoHide); + builder_.add_scrollbarEnabeld(scrollbarEnabeld); builder_.add_bounceEnabled(bounceEnabled); builder_.add_backGroundScale9Enabled(backGroundScale9Enabled); builder_.add_bgColorOpacity(bgColorOpacity); diff --git a/cocos/editor-support/cocostudio/WidgetReader/NodeReaderDefine.h b/cocos/editor-support/cocostudio/WidgetReader/NodeReaderDefine.h index df13c00ddb..15554a2123 100644 --- a/cocos/editor-support/cocostudio/WidgetReader/NodeReaderDefine.h +++ b/cocos/editor-support/cocostudio/WidgetReader/NodeReaderDefine.h @@ -47,4 +47,6 @@ cocos2d::ObjectFactory::TInfo className::__Type(#className, &className::createIn #define CREATE_CLASS_NODE_READER_INFO(className) \ cocos2d::ObjectFactory::TInfo(#className, &className::createInstance) \ +#define FLATSTR_TO_BOOL(str) (str.compare("True") == 0) ? true : false + #endif /* defined(__cocos2d_libs__NodeReaderDefine__) */ diff --git a/cocos/editor-support/cocostudio/WidgetReader/ScrollViewReader/ScrollViewReader.cpp b/cocos/editor-support/cocostudio/WidgetReader/ScrollViewReader/ScrollViewReader.cpp index 14914cd199..576fc306bc 100644 --- a/cocos/editor-support/cocostudio/WidgetReader/ScrollViewReader/ScrollViewReader.cpp +++ b/cocos/editor-support/cocostudio/WidgetReader/ScrollViewReader/ScrollViewReader.cpp @@ -20,21 +20,21 @@ namespace cocostudio static const char* P_InnerHeight = "innerHeight"; static const char* P_Direction = "direction"; static const char* P_BounceEnable = "bounceEnable"; - + static ScrollViewReader* instanceScrollViewReader = nullptr; - + IMPLEMENT_CLASS_NODE_READER_INFO(ScrollViewReader) - - ScrollViewReader::ScrollViewReader() + + ScrollViewReader::ScrollViewReader() { - + } - + ScrollViewReader::~ScrollViewReader() { - + } - + ScrollViewReader* ScrollViewReader::getInstance() { if (!instanceScrollViewReader) @@ -43,19 +43,19 @@ namespace cocostudio } return instanceScrollViewReader; } - + void ScrollViewReader::destroyInstance() { CC_SAFE_DELETE(instanceScrollViewReader); } - + void ScrollViewReader::setPropsFromBinary(cocos2d::ui::Widget *widget, CocoLoader *cocoLoader, stExpCocoNode* cocoNode) { //TODO: need to refactor... LayoutReader::setPropsFromBinary(widget, cocoLoader, cocoNode); - + ScrollView* scrollView = static_cast(widget); - + stExpCocoNode *stChildArray = cocoNode->GetChildArray(cocoLoader); float innerWidth; float innerHeight; @@ -65,46 +65,48 @@ namespace cocostudio if (key == P_InnerWidth) { innerWidth = valueToFloat(value); } - else if(key == P_InnerHeight){ + else if (key == P_InnerHeight) { innerHeight = valueToFloat(value); - }else if(key == P_Direction){ + } + else if (key == P_Direction) { scrollView->setDirection((ScrollView::Direction)valueToInt(value)); - }else if(key == P_BounceEnable){ + } + else if (key == P_BounceEnable) { scrollView->setBounceEnabled(valueToBool(value)); } - + } //end of for loop scrollView->setInnerContainerSize(Size(innerWidth, innerHeight)); } - + void ScrollViewReader::setPropsFromJsonDictionary(Widget *widget, const rapidjson::Value &options) { LayoutReader::setPropsFromJsonDictionary(widget, options); - - + + ScrollView* scrollView = static_cast(widget); - float innerWidth = DICTOOL->getFloatValue_json(options, P_InnerWidth,200); - float innerHeight = DICTOOL->getFloatValue_json(options, P_InnerHeight,200); + float innerWidth = DICTOOL->getFloatValue_json(options, P_InnerWidth, 200); + float innerHeight = DICTOOL->getFloatValue_json(options, P_InnerHeight, 200); scrollView->setInnerContainerSize(Size(innerWidth, innerHeight)); - int direction = DICTOOL->getFloatValue_json(options, P_Direction,1); + int direction = DICTOOL->getFloatValue_json(options, P_Direction, 1); scrollView->setDirection((ScrollView::Direction)direction); scrollView->setBounceEnabled(DICTOOL->getBooleanValue_json(options, P_BounceEnable)); - - + + LayoutReader::setColorPropsFromJsonDictionary(widget, options); - } - + } + Offset ScrollViewReader::createOptionsWithFlatBuffers(const tinyxml2::XMLElement *objectData, - flatbuffers::FlatBufferBuilder *builder) + flatbuffers::FlatBufferBuilder *builder) { auto temp = WidgetReader::getInstance()->createOptionsWithFlatBuffers(objectData, builder); auto widgetOptions = *(Offset*)(&temp); - + std::string path = ""; std::string plistFile = ""; int resourceType = 0; - + bool clipEnabled = false; Color3B bgColor; Color3B bgStartColor; @@ -118,18 +120,20 @@ namespace cocostudio Size innerSize(200, 300); int direction = 0; bool bounceEnabled = false; - - + bool scrollbarEnabled = true; + bool scrollbarAutoHide = true; + float scrollbarAutoHideTime = 0.2f; + // attributes const tinyxml2::XMLAttribute* attribute = objectData->FirstAttribute(); while (attribute) { std::string name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "ClipAble") { - clipEnabled = (value == "True") ? true : false; + clipEnabled = FLATSTR_TO_BOOL(value); } else if (name == "ComboBoxIndex") { @@ -141,10 +145,7 @@ namespace cocostudio } else if (name == "Scale9Enable") { - if (value == "True") - { - backGroundScale9Enabled = true; - } + backGroundScale9Enabled = FLATSTR_TO_BOOL(value); } else if (name == "Scale9OriginX") { @@ -179,18 +180,29 @@ namespace cocostudio } else if (name == "IsBounceEnabled") { - bounceEnabled = (value == "True") ? true : false; + bounceEnabled = FLATSTR_TO_BOOL(value); + } + else if (name.compare("BarEnabled") == 0) + { + scrollbarEnabled = FLATSTR_TO_BOOL(value); + } + else if (name.compare("BarAutoHide") == 0) + { + scrollbarAutoHide = FLATSTR_TO_BOOL(value); + } + else if (name.compare("BarAutoHideTime") == 0) + { + scrollbarAutoHideTime = atof(value.c_str()); } - attribute = attribute->Next(); } - + // child elements const tinyxml2::XMLElement* child = objectData->FirstChildElement(); while (child) { std::string name = child->Name(); - + if (name == "InnerNodeSize") { attribute = child->FirstAttribute(); @@ -198,7 +210,7 @@ namespace cocostudio { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "Width") { innerSize.width = atof(value.c_str()); @@ -207,19 +219,19 @@ namespace cocostudio { innerSize.height = atof(value.c_str()); } - + attribute = attribute->Next(); } } else if (name == "Size" && backGroundScale9Enabled) { attribute = child->FirstAttribute(); - + while (attribute) { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "X") { scale9Size.width = atof(value.c_str()); @@ -228,19 +240,19 @@ namespace cocostudio { scale9Size.height = atof(value.c_str()); } - + attribute = attribute->Next(); } } else if (name == "SingleColor") { attribute = child->FirstAttribute(); - + while (attribute) { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "R") { bgColor.r = atoi(value.c_str()); @@ -253,19 +265,19 @@ namespace cocostudio { bgColor.b = atoi(value.c_str()); } - + attribute = attribute->Next(); } } else if (name == "EndColor") { attribute = child->FirstAttribute(); - + while (attribute) { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "R") { bgEndColor.r = atoi(value.c_str()); @@ -278,19 +290,19 @@ namespace cocostudio { bgEndColor.b = atoi(value.c_str()); } - + attribute = attribute->Next(); } } else if (name == "FirstColor") { attribute = child->FirstAttribute(); - + while (attribute) { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "R") { bgStartColor.r = atoi(value.c_str()); @@ -303,7 +315,7 @@ namespace cocostudio { bgStartColor.b = atoi(value.c_str()); } - + attribute = attribute->Next(); } } @@ -314,7 +326,7 @@ namespace cocostudio { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "ScaleX") { colorVector.x = atof(value.c_str()); @@ -323,7 +335,7 @@ namespace cocostudio { colorVector.y = atof(value.c_str()); } - + attribute = attribute->Next(); } } @@ -331,14 +343,14 @@ namespace cocostudio { std::string texture = ""; std::string texturePng = ""; - + attribute = child->FirstAttribute(); - + while (attribute) { name = attribute->Name(); std::string value = attribute->Value(); - + if (name == "Path") { path = value; @@ -352,20 +364,20 @@ namespace cocostudio plistFile = value; texture = value; } - + attribute = attribute->Next(); } - + if (resourceType == 1) { FlatBuffersSerialize* fbs = FlatBuffersSerialize::getInstance(); - fbs->_textures.push_back(builder->CreateString(texture)); + fbs->_textures.push_back(builder->CreateString(texture)); } } - + child = child->NextSiblingElement(); } - + Color f_bgColor(255, bgColor.r, bgColor.g, bgColor.b); Color f_bgStartColor(255, bgStartColor.r, bgStartColor.g, bgStartColor.b); Color f_bgEndColor(255, bgEndColor.r, bgEndColor.g, bgEndColor.b); @@ -392,44 +404,47 @@ namespace cocostudio backGroundScale9Enabled, &f_innerSize, direction, - bounceEnabled); + bounceEnabled, + scrollbarEnabled, + scrollbarAutoHide, + scrollbarAutoHideTime); return *(Offset
*)(&options); } - + void ScrollViewReader::setPropsWithFlatBuffers(cocos2d::Node *node, const flatbuffers::Table *scrollViewOptions) { ScrollView* scrollView = static_cast(node); auto options = (ScrollViewOptions*)scrollViewOptions; - + bool clipEnabled = options->clipEnabled() != 0; scrollView->setClippingEnabled(clipEnabled); - + bool backGroundScale9Enabled = options->backGroundScale9Enabled() != 0; scrollView->setBackGroundImageScale9Enabled(backGroundScale9Enabled); - - + + auto f_bgColor = options->bgColor(); Color3B bgColor(f_bgColor->r(), f_bgColor->g(), f_bgColor->b()); auto f_bgStartColor = options->bgStartColor(); Color3B bgStartColor(f_bgStartColor->r(), f_bgStartColor->g(), f_bgStartColor->b()); auto f_bgEndColor = options->bgEndColor(); Color3B bgEndColor(f_bgEndColor->r(), f_bgEndColor->g(), f_bgEndColor->b()); - + auto f_colorVecor = options->colorVector(); Vec2 colorVector(f_colorVecor->vectorX(), f_colorVecor->vectorY()); scrollView->setBackGroundColorVector(colorVector); - + int bgColorOpacity = options->bgColorOpacity(); - + int colorType = options->colorType(); scrollView->setBackGroundColorType(Layout::BackGroundColorType(colorType)); - + scrollView->setBackGroundColor(bgStartColor, bgEndColor); scrollView->setBackGroundColor(bgColor); scrollView->setBackGroundColorOpacity(bgColorOpacity); - - + + bool fileExist = false; std::string errorFilePath = ""; auto imageFileNameDic = options->backGroundImageData(); @@ -439,51 +454,51 @@ namespace cocostudio { switch (imageFileNameType) { - case 0: + case 0: + { + if (FileUtils::getInstance()->isFileExist(imageFileName)) { - if (FileUtils::getInstance()->isFileExist(imageFileName)) + fileExist = true; + } + else + { + errorFilePath = imageFileName; + fileExist = false; + } + break; + } + + case 1: + { + std::string plist = imageFileNameDic->plistFile()->c_str(); + SpriteFrame* spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imageFileName); + if (spriteFrame) + { + fileExist = true; + } + else + { + if (FileUtils::getInstance()->isFileExist(plist)) { - fileExist = true; + ValueMap value = FileUtils::getInstance()->getValueMapFromFile(plist); + ValueMap metadata = value["metadata"].asValueMap(); + std::string textureFileName = metadata["textureFileName"].asString(); + if (!FileUtils::getInstance()->isFileExist(textureFileName)) + { + errorFilePath = textureFileName; + } } else { - errorFilePath = imageFileName; - fileExist = false; + errorFilePath = plist; } - break; + fileExist = false; } - - case 1: - { - std::string plist = imageFileNameDic->plistFile()->c_str(); - SpriteFrame* spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imageFileName); - if (spriteFrame) - { - fileExist = true; - } - else - { - if (FileUtils::getInstance()->isFileExist(plist)) - { - ValueMap value = FileUtils::getInstance()->getValueMapFromFile(plist); - ValueMap metadata = value["metadata"].asValueMap(); - std::string textureFileName = metadata["textureFileName"].asString(); - if (!FileUtils::getInstance()->isFileExist(textureFileName)) - { - errorFilePath = textureFileName; - } - } - else - { - errorFilePath = plist; - } - fileExist = false; - } - break; - } - - default: - break; + break; + } + + default: + break; } if (fileExist) { @@ -496,15 +511,15 @@ namespace cocostudio // scrollView->addChild(label); //} } - + auto widgetOptions = options->widgetOptions(); auto f_color = widgetOptions->color(); Color3B color(f_color->r(), f_color->g(), f_color->b()); scrollView->setColor(color); - + int opacity = widgetOptions->alpha(); scrollView->setOpacity(opacity); - + auto f_innerSize = options->innerSize(); Size innerSize(f_innerSize->width(), f_innerSize->height()); scrollView->setInnerContainerSize(innerSize); @@ -512,17 +527,26 @@ namespace cocostudio scrollView->setDirection((ScrollView::Direction)direction); bool bounceEnabled = options->bounceEnabled() != 0; scrollView->setBounceEnabled(bounceEnabled); - - + + bool scrollbarEnabled = options->scrollbarEnabeld() != 0; + scrollView->setScrollBarEnabled(scrollbarEnabled); + bool scrollbarAutoHide = options->scrollbarAutoHide() != 0; + if (scrollbarEnabled) + { + scrollView->setScrollBarAutoHideEnabled(scrollbarAutoHide); + float barAutoHideTime = options->scrollbarAutoHideTime(); + scrollView->setScrollBarAutoHideTime(barAutoHideTime); + } + auto widgetReader = WidgetReader::getInstance(); widgetReader->setPropsWithFlatBuffers(node, (Table*)options->widgetOptions()); - + if (backGroundScale9Enabled) { auto f_capInsets = options->capInsets(); Rect capInsets(f_capInsets->x(), f_capInsets->y(), f_capInsets->width(), f_capInsets->height()); scrollView->setBackGroundImageCapInsets(capInsets); - + auto f_scale9Size = options->scale9Size(); Size scale9Size(f_scale9Size->width(), f_scale9Size->height()); scrollView->setContentSize(scale9Size); @@ -535,34 +559,34 @@ namespace cocostudio scrollView->setContentSize(contentSize); } } - + } - + Node* ScrollViewReader::createNodeWithFlatBuffers(const flatbuffers::Table *scrollViewOptions) { ScrollView* scrollView = ScrollView::create(); - + setPropsWithFlatBuffers(scrollView, (Table*)scrollViewOptions); - + return scrollView; } - + int ScrollViewReader::getResourceType(std::string key) { - if(key == "Normal" || key == "Default") + if (key == "Normal" || key == "Default") { return 0; } - + FlatBuffersSerialize* fbs = FlatBuffersSerialize::getInstance(); - if(fbs->_isSimulator) + if (fbs->_isSimulator) { - if(key == "MarkedSubImage") + if (key == "MarkedSubImage") { return 0; } } return 1; } - + } diff --git a/cocos/network/HttpRequest.h b/cocos/network/HttpRequest.h index 1e13f9d1e6..c4255189da 100644 --- a/cocos/network/HttpRequest.h +++ b/cocos/network/HttpRequest.h @@ -142,9 +142,9 @@ public: * Set the url address of HttpRequest object. * The url value could be like these: "http://httpbin.org/ip" or "https://httpbin.org/get" * - * @param url the string pointer. + * @param url the string object. */ - inline void setUrl(const char* url) + inline void setUrl(const std::string& url) { _url = url; }; @@ -194,9 +194,9 @@ public: * Set a string tag to identify your request. * This tag can be found in HttpResponse->getHttpRequest->getTag(). * - * @param tag the string pointer + * @param tag the string object. */ - inline void setTag(const char* tag) + inline void setTag(const std::string& tag) { _tag = tag; }; diff --git a/cocos/platform/android/Android.mk b/cocos/platform/android/Android.mk index 5e36900388..a7a816f888 100644 --- a/cocos/platform/android/Android.mk +++ b/cocos/platform/android/Android.mk @@ -19,8 +19,7 @@ jni/Java_org_cocos2dx_lib_Cocos2dxBitmap.cpp \ jni/Java_org_cocos2dx_lib_Cocos2dxHelper.cpp \ jni/Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp \ jni/JniHelper.cpp \ -jni/TouchesJni.cpp \ -jni/CocosPlayClient.cpp +jni/TouchesJni.cpp LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) diff --git a/cocos/platform/android/CCFileUtils-android.cpp b/cocos/platform/android/CCFileUtils-android.cpp index 9c3a1b2050..bf055f97dd 100644 --- a/cocos/platform/android/CCFileUtils-android.cpp +++ b/cocos/platform/android/CCFileUtils-android.cpp @@ -29,7 +29,6 @@ THE SOFTWARE. #include "CCFileUtils-android.h" #include "platform/CCCommon.h" #include "platform/android/jni/JniHelper.h" -#include "platform/android/jni/CocosPlayClient.h" #include "android/asset_manager.h" #include "android/asset_manager_jni.h" #include @@ -78,15 +77,7 @@ FileUtilsAndroid::~FileUtilsAndroid() bool FileUtilsAndroid::init() { - cocosplay::lazyInit(); - if (cocosplay::isEnabled() && !cocosplay::isDemo()) - { - _defaultResRootPath = cocosplay::getGameRoot(); - } - else - { - _defaultResRootPath = "assets/"; - } + _defaultResRootPath = "assets/"; return FileUtils::init(); } @@ -150,11 +141,6 @@ bool FileUtilsAndroid::isFileExistInternal(const std::string& strFilePath) const return false; } - if (cocosplay::isEnabled() && !cocosplay::isDemo()) - { - return cocosplay::fileExists(strFilePath); - } - bool bFound = false; // Check whether file exists in apk. @@ -200,16 +186,6 @@ bool FileUtilsAndroid::isDirectoryExistInternal(const std::string& dirPath) cons int lenOfAssets = 7; std::string tmpStr; - if (cocosplay::isEnabled() && !cocosplay::isDemo()) - { - // redirect assets/*** path to cocosplay resource dir - tmpStr.append(_defaultResRootPath); - if ('/' != tmpStr[tmpStr.length() - 1]) - { - tmpStr += '/'; - } - tmpStr.append(s + lenOfAssets); - } // find absolute path in flash memory if (s[0] == '/') @@ -264,7 +240,6 @@ Data FileUtilsAndroid::getData(const std::string& filename, bool forString) unsigned char* data = nullptr; ssize_t size = 0; string fullPath = fullPathForFilename(filename); - cocosplay::updateAssets(fullPath); if (fullPath[0] != '/') { @@ -356,7 +331,6 @@ Data FileUtilsAndroid::getData(const std::string& filename, bool forString) else { ret.fastSet(data, size); - cocosplay::notifyFileLoaded(fullPath); } return ret; @@ -387,7 +361,6 @@ unsigned char* FileUtilsAndroid::getFileData(const std::string& filename, const } string fullPath = fullPathForFilename(filename); - cocosplay::updateAssets(fullPath); if (fullPath[0] != '/') { @@ -459,10 +432,7 @@ unsigned char* FileUtilsAndroid::getFileData(const std::string& filename, const msg.append(filename).append(") failed!"); CCLOG("%s", msg.c_str()); } - else - { - cocosplay::notifyFileLoaded(fullPath); - } + return data; } diff --git a/cocos/platform/android/java/src/com/chukong/cocosplay/client/CocosPlayClient.java b/cocos/platform/android/java/src/com/chukong/cocosplay/client/CocosPlayClient.java deleted file mode 100644 index 06a869034c..0000000000 --- a/cocos/platform/android/java/src/com/chukong/cocosplay/client/CocosPlayClient.java +++ /dev/null @@ -1,59 +0,0 @@ -/**************************************************************************** -Copyright (c) 2015 Chukong Technologies Inc. - -http://www.cocos2d-x.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ****************************************************************************/ -package com.chukong.cocosplay.client; - -import android.app.Activity; - -public class CocosPlayClient { - - public static boolean init(Activity activity, boolean isDemo) { - return false; - } - - public static boolean isEnabled() { - return false; - } - - public static boolean isDemo() { - return false; - } - - public static boolean isNotifyFileLoadedEnabled() { - return false; - } - - public static void notifyFileLoaded(String filePath) { - - } - - public static void updateAssets(String filePath) { - - } - - public static String getGameRoot() { - return ""; - } - - public static native String[] getSearchPaths(); -} diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java index 851e1b4bfe..75b4f95e21 100644 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java @@ -42,8 +42,6 @@ import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; -import com.chukong.cocosplay.client.CocosPlayClient; - import org.cocos2dx.lib.Cocos2dxHelper.Cocos2dxHelperListener; import javax.microedition.khronos.egl.EGL10; @@ -258,7 +256,6 @@ public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelpe @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - CocosPlayClient.init(this, false); onLoadNativeLibraries(); diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHelper.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHelper.java index 282382da6f..9cd24e82f9 100644 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHelper.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHelper.java @@ -41,7 +41,6 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; -import com.chukong.cocosplay.client.CocosPlayClient; import com.enhance.gameservice.IGameTuningService; import java.io.UnsupportedEncodingException; @@ -94,12 +93,7 @@ public class Cocos2dxHelper { final ApplicationInfo applicationInfo = activity.getApplicationInfo(); Cocos2dxHelper.sPackageName = applicationInfo.packageName; - if (CocosPlayClient.isEnabled() && !CocosPlayClient.isDemo()) { - Cocos2dxHelper.sFileDirectory = CocosPlayClient.getGameRoot(); - } - else { - Cocos2dxHelper.sFileDirectory = activity.getFilesDir().getAbsolutePath(); - } + Cocos2dxHelper.sFileDirectory = activity.getFilesDir().getAbsolutePath(); Cocos2dxHelper.nativeSetApkPath(applicationInfo.sourceDir); diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java index e9bae8458e..0e9f2e499f 100644 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java @@ -30,8 +30,6 @@ import android.content.res.AssetFileDescriptor; import android.media.MediaPlayer; import android.util.Log; -import com.chukong.cocosplay.client.CocosPlayClient; - import java.io.FileInputStream; public class Cocos2dxMusic { @@ -247,10 +245,6 @@ public class Cocos2dxMusic { MediaPlayer mediaPlayer = new MediaPlayer(); try { - if (CocosPlayClient.isEnabled() && !CocosPlayClient.isDemo()) { - CocosPlayClient.updateAssets(path); - } - CocosPlayClient.notifyFileLoaded(path); if (path.startsWith("/")) { final FileInputStream fis = new FileInputStream(path); mediaPlayer.setDataSource(fis.getFD()); diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxSound.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxSound.java index 688de896fb..a54317020d 100644 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxSound.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxSound.java @@ -29,8 +29,6 @@ import android.media.AudioManager; import android.media.SoundPool; import android.util.Log; -import com.chukong.cocosplay.client.CocosPlayClient; - import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -98,10 +96,6 @@ public class Cocos2dxSound { } public int preloadEffect(final String path) { - if (CocosPlayClient.isEnabled() && !CocosPlayClient.isDemo()) { - CocosPlayClient.updateAssets(path); - } - CocosPlayClient.notifyFileLoaded(path); Integer soundID = this.mPathSoundIDMap.get(path); if (soundID == null) { diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxVideoView.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxVideoView.java index 04e7d7c6b8..b4a2e7848c 100644 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxVideoView.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxVideoView.java @@ -34,8 +34,6 @@ import android.view.SurfaceView; import android.widget.FrameLayout; import android.widget.MediaController.MediaPlayerControl; -import com.chukong.cocosplay.client.CocosPlayClient; - import java.io.IOException; import java.util.Map; @@ -219,10 +217,6 @@ public class Cocos2dxVideoView extends SurfaceView implements MediaPlayerControl if (path.startsWith(AssetResourceRoot)) { path = path.substring(AssetResourceRoot.length()); } - if (CocosPlayClient.isEnabled() && !CocosPlayClient.isDemo()) { - CocosPlayClient.updateAssets(path); - } - CocosPlayClient.notifyFileLoaded(path); if (path.startsWith("/")) { mIsAssetRouse = false; setVideoURI(Uri.parse(path),null); diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxWebViewHelper.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxWebViewHelper.java index 1b11c9a879..0d036215a9 100755 --- a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxWebViewHelper.java +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxWebViewHelper.java @@ -6,8 +6,6 @@ import android.util.SparseArray; import android.view.View; import android.widget.FrameLayout; -import com.chukong.cocosplay.client.CocosPlayClient; - import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; @@ -157,10 +155,6 @@ public class Cocos2dxWebViewHelper { } public static void loadFile(final int index, final String filePath) { - if (CocosPlayClient.isEnabled() && !CocosPlayClient.isDemo()) { - CocosPlayClient.updateAssets(filePath); - } - CocosPlayClient.notifyFileLoaded(filePath); sCocos2dxActivity.runOnUiThread(new Runnable() { @Override public void run() { diff --git a/cocos/platform/android/jni/CocosPlayClient.cpp b/cocos/platform/android/jni/CocosPlayClient.cpp deleted file mode 100644 index 13ecdf03bd..0000000000 --- a/cocos/platform/android/jni/CocosPlayClient.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/**************************************************************************** -Copyright (c) 2015 Chukong Technologies Inc. - -http://www.cocos2d-x.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ -#include "CocosPlayClient.h" -#include -#include -#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) -#include "jni/JniHelper.h" -#include "platform/CCCommon.h" -#include "platform/CCFileUtils.h" - -using namespace cocos2d; - -#define LOG_TAG "CocosPlayClient.cpp" -#if COCOS2D_DEBUG -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) -#else -#define LOGD(...) -#define LOGW(...) -#define LOGE(...) -#endif - -static std::string __gameRootPath; -static std::unordered_map __fileExistsCaches; -static bool __isCocosPlayInited = false; -static bool __isCocosPlayEnabled = false; -static bool __isDemo = false; -static bool __isNotifyFileLoadedEnabled = false; -static jobject __classLoader; -static jmethodID __findClassMethod; -static pthread_key_t __threadKey; - - -#define COCOSPLAYCLIENT_CLASS_NAME "com/chukong/cocosplay/client/CocosPlayClient" - -extern "C" { - jobjectArray Java_com_chukong_cocosplay_client_CocosPlayClient_getSearchPaths(JNIEnv* env, jobject thiz) - { - auto stringClass = env->FindClass("java/lang/String"); - auto& paths = cocos2d::FileUtils::getInstance()->getSearchPaths(); - auto count = paths.size(); - auto pathArray = env->NewObjectArray(count, stringClass, 0); - for (int i = 0; i < count; ++i) - { - env->SetObjectArrayElement(pathArray, i, env->NewStringUTF(paths[i].c_str())); - } - - return pathArray; - } -} - -namespace cocosplay { - -static void detachCurrentThread(void *env) { - JniHelper::getJavaVM()->DetachCurrentThread(); -} - -static bool getEnv(JNIEnv **env) -{ - bool bRet = false; - - switch(JniHelper::getJavaVM()->GetEnv((void**)env, JNI_VERSION_1_4)) - { - case JNI_OK: - bRet = true; - break; - case JNI_EDETACHED: - pthread_key_create (&__threadKey, detachCurrentThread); - if (JniHelper::getJavaVM()->AttachCurrentThread(env, 0) < 0) - { - LOGD("%s", "Failed to get the environment using AttachCurrentThread()"); - break; - } - if (pthread_getspecific(__threadKey) == NULL) { - pthread_setspecific(__threadKey, env); - } - bRet = true; - break; - default: - LOGE("%s", "Failed to get the environment using GetEnv()"); - break; - } - - return bRet; -} - -static void initClassLoaderForMultiThread() -{ - JNIEnv *env = 0; - do - { - if (! getEnv(&env)) - { - break; - } - jclass cocos2dClass = env->FindClass(COCOSPLAYCLIENT_CLASS_NAME); - if(env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - LOGW("Exception initClassLoaderForMultiThread cocos2dClass is exception"); - break; - } - - jclass classClass = env->GetObjectClass(cocos2dClass); - if(env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - LOGW("Exception initClassLoaderForMultiThread classClass is exception"); - break; - } - - jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); - if(env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - LOGW("Exception initClassLoaderForMultiThread classLoaderClass"); - break; - } - - jmethodID getClassLoaderMethod = env->GetMethodID(classClass,"getClassLoader","()Ljava/lang/ClassLoader;"); - jobject classLoader = env->CallObjectMethod(cocos2dClass, getClassLoaderMethod); - if(env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - LOGW("Exception initClassLoaderForMultiThread classLoader"); - break; - } - __classLoader = env->NewGlobalRef(classLoader); - jmethodID findClassMethod = env->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - __findClassMethod = findClassMethod; - - if(env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - __findClassMethod = NULL; - __classLoader = NULL; - LOGW("Exception initClassLoaderForMultiThread findClassMethod"); - break; - } - }while(0); -} - -static jclass getClassID_(const char *className, JNIEnv *env) -{ - JNIEnv *pEnv = env; - jclass ret = 0; - - do - { - if (! pEnv) - { - if (! getEnv(&pEnv)) - { - break; - } - } - - ret = pEnv->FindClass(className); - if (! ret) - { - if(__classLoader) - { - pEnv->ExceptionClear(); - jstring jstrName = (pEnv)->NewStringUTF(className); - ret = (jclass)pEnv->CallObjectMethod(__classLoader, __findClassMethod, jstrName); - pEnv->DeleteLocalRef(jstrName); - if(ret) break; - } - - LOGE("Failed to find class of %s", className); - break; - } - } while (0); - - return ret; -} - -static bool getStaticMethodInfo(cocos2d::JniMethodInfo &methodinfo, const char *className, const char *methodName, const char *paramCode) -{ - jmethodID methodID = 0; - JNIEnv *pEnv = 0; - bool bRet = false; - - do - { - if (! getEnv(&pEnv)) - { - break; - } - - jclass classID = getClassID_(className, pEnv); - if(!classID) break; - - methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode); - if (! methodID) - { - LOGW("Failed to find static method id of %s", methodName); - break; - } - - methodinfo.classID = classID; - methodinfo.env = pEnv; - methodinfo.methodID = methodID; - - bRet = true; - } while (0); - - return bRet; -} - -void lazyInit() -{ - if (__isCocosPlayInited) - return; - - JniMethodInfo t; - if (JniHelper::getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "isEnabled", "()Z")) - { - __isCocosPlayEnabled = t.env->CallStaticBooleanMethod(t.classID, t.methodID); - t.env->DeleteLocalRef(t.classID); - LOGD("isEnabled = %d", __isCocosPlayEnabled); - } - - if (JniHelper::getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "isDemo", "()Z")) - { - __isDemo = t.env->CallStaticBooleanMethod(t.classID, t.methodID); - t.env->DeleteLocalRef(t.classID); - LOGD("isDemo = %d",__isDemo); - } - - if (JniHelper::getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "isNotifyFileLoadedEnabled", "()Z")) - { - __isNotifyFileLoadedEnabled = t.env->CallStaticBooleanMethod(t.classID, t.methodID); - t.env->DeleteLocalRef(t.classID); - LOGD("isNotifyFileLoadedEnabled = %d", __isNotifyFileLoadedEnabled); - } - - if (__isCocosPlayEnabled) - { - initClassLoaderForMultiThread(); - } - - __isCocosPlayInited = true; -} - -bool isEnabled() -{ - return __isCocosPlayEnabled; -} - -bool isDemo() -{ - return __isDemo; -} - -void updateAssets(const std::string& filePath) -{ - if (!__isCocosPlayInited) - { - lazyInit(); - } - - if (!__isCocosPlayEnabled || __isDemo) - { - return; - } - - if (!fileExists(filePath)) - { - LOGD("file ( %s ) doesn't exist, updateAssets cancelled", filePath.c_str()); - return; - } - - JniMethodInfo t; - if (getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "updateAssets", "(Ljava/lang/String;)V")) - { - jstring stringArg = t.env->NewStringUTF(filePath.c_str()); - t.env->CallStaticVoidMethod(t.classID, t.methodID, stringArg); - t.env->DeleteLocalRef(stringArg); - t.env->DeleteLocalRef(t.classID); - } - - LOGD("updateAssets (%s) OK!", filePath.c_str()); -} - -bool fileExists(const std::string& filePath) -{ - auto iter = __fileExistsCaches.find(filePath); - if (iter != __fileExistsCaches.end()) - { - LOGD("Return file path ( %s ) in cache!", filePath.c_str()); - if(!iter->second) - { - auto fp = fopen(filePath.c_str(), "r"); - if (fp) - { - iter->second = true; - fclose(fp); - } - } - return iter->second; - } - - bool ret = false; - JniMethodInfo t; - if (getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "fileExists", "(Ljava/lang/String;)Z")) - { - jstring stringArg = t.env->NewStringUTF(filePath.c_str()); - ret = t.env->CallStaticBooleanMethod(t.classID, t.methodID, stringArg); - t.env->DeleteLocalRef(stringArg); - t.env->DeleteLocalRef(t.classID); - } - - __fileExistsCaches[filePath] = ret; - - LOGD("fileExists return (%d), path (%s)!", ret, filePath.c_str()); - return ret; -} - -void notifyFileLoaded(const std::string& filePath) -{ - if (!__isNotifyFileLoadedEnabled) - return; - - JniMethodInfo t; - if (getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "notifyFileLoaded", "(Ljava/lang/String;)V")) - { - jstring stringArg = t.env->NewStringUTF(filePath.c_str()); - t.env->CallStaticVoidMethod(t.classID, t.methodID, stringArg); - t.env->DeleteLocalRef(stringArg); - t.env->DeleteLocalRef(t.classID); - } -} - -std::string getGameRoot() -{ - if (!__isCocosPlayEnabled) - { - LOGW("CocosPlayClient isn't enabled!"); - return ""; - } - - if (__gameRootPath.empty()) - { - JniMethodInfo t; - if (JniHelper::getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "getGameRoot", "()Ljava/lang/String;")) - { - jstring str = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID); - __gameRootPath = JniHelper::jstring2string(str); - t.env->DeleteLocalRef(str); - t.env->DeleteLocalRef(t.classID); - } - LOGD("GameRoot : %s", __gameRootPath.c_str()); - } - return __gameRootPath; -} - -void purgeCachedEntries() -{ - __fileExistsCaches.clear(); -} - -void purgeCachedByFile(const std::string& filePath) -{ - __fileExistsCaches.erase(filePath); -} - -void notifyDemoEnded() -{ - JniMethodInfo t; - if (JniHelper::getStaticMethodInfo(t, COCOSPLAYCLIENT_CLASS_NAME, "notifyDemoEnded", "()V")) - { - t.env->CallStaticVoidMethod(t.classID, t.methodID); - t.env->DeleteLocalRef(t.classID); - LOGD("Game demo was ended!"); - } -} - -} // namespace cocosplay { - -#else - -namespace cocosplay { - -bool isEnabled() { return false; } -bool isDemo() { return false; } -void updateAssets(const std::string& filePath) {} -bool fileExists(const std::string& filePath) { return false; } -void notifyFileLoaded(const std::string& filePath) {} -std::string getGameRoot() { return ""; } -void purgeCachedEntries() {} -void notifyDemoEnded() {} -void purgeCachedByFile(const std::string& filePath){} -} // namespace cocosplay { - -#endif diff --git a/cocos/platform/android/jni/CocosPlayClient.h b/cocos/platform/android/jni/CocosPlayClient.h deleted file mode 100644 index 9c44f26795..0000000000 --- a/cocos/platform/android/jni/CocosPlayClient.h +++ /dev/null @@ -1,86 +0,0 @@ -/**************************************************************************** -Copyright (c) 2015 Chukong Technologies Inc. - -http://www.cocos2d-x.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ -#ifndef __COCOSPLAYCLIENT_H__ -#define __COCOSPLAYCLIENT_H__ - -#include - -namespace cocosplay { - -void lazyInit(); - -/** - * Checks whether CocosPlay is enabled - */ -bool isEnabled(); - -/** - * Checks whether CocosPlay is in demo mode - */ -bool isDemo(); - -/** - * Updates assets by filePath, if the file doesn't exist, CocosPlay will show a progress page of downloading. - * And this interface will be hung up until the scene package was downloaded. - */ -void updateAssets(const std::string& filePath); - -/** - * Checks whether the file exists - */ -bool fileExists(const std::string& filePath); - -/** - * Notifies to Cocos Play SDK that a file was loaded - * It will do nothing if game doesn't run on Cocos Play - */ -void notifyFileLoaded(const std::string& filePath); - -/** - * Gets the resource root path of current game - * @return A writable path of current game - */ -std::string getGameRoot(); - -/** - * Purges the file searching cache. - * - * @note It should be invoked after the resources were updated. - * For instance, it could be used when there is a small update in games. - */ -void purgeCachedEntries(); - -/** - * Purges the file searching cache by giving file path. - */ -void purgeCachedByFile(const std::string& filePath); - -/** - * Notifies that the game demo was ended - */ -void notifyDemoEnded(); - -} // namespace cocosplay { - -#endif // __COCOSPLAYCLIENT_H__ diff --git a/cocos/platform/desktop/CCGLViewImpl-desktop.cpp b/cocos/platform/desktop/CCGLViewImpl-desktop.cpp index 65a733fab8..665ffa3440 100644 --- a/cocos/platform/desktop/CCGLViewImpl-desktop.cpp +++ b/cocos/platform/desktop/CCGLViewImpl-desktop.cpp @@ -301,7 +301,7 @@ GLViewImpl::~GLViewImpl() GLViewImpl* GLViewImpl::create(const std::string& viewName) { auto ret = new (std::nothrow) GLViewImpl; - if(ret && ret->initWithRect(viewName, Rect(0, 0, 960, 640), 1)) { + if(ret && ret->initWithRect(viewName, Rect(0, 0, 960, 640), 1.0f, false)) { ret->autorelease(); return ret; } @@ -309,10 +309,10 @@ GLViewImpl* GLViewImpl::create(const std::string& viewName) return nullptr; } -GLViewImpl* GLViewImpl::createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor) +GLViewImpl* GLViewImpl::createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable) { auto ret = new (std::nothrow) GLViewImpl; - if(ret && ret->initWithRect(viewName, rect, frameZoomFactor)) { + if(ret && ret->initWithRect(viewName, rect, frameZoomFactor, resizable)) { ret->autorelease(); return ret; } @@ -342,13 +342,13 @@ GLViewImpl* GLViewImpl::createWithFullScreen(const std::string& viewName, const return nullptr; } -bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor) +bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable) { setViewName(viewName); _frameZoomFactor = frameZoomFactor; - glfwWindowHint(GLFW_RESIZABLE,GL_FALSE); + glfwWindowHint(GLFW_RESIZABLE,resizable?GL_TRUE:GL_FALSE); glfwWindowHint(GLFW_RED_BITS,_glContextAttrs.redBits); glfwWindowHint(GLFW_GREEN_BITS,_glContextAttrs.greenBits); glfwWindowHint(GLFW_BLUE_BITS,_glContextAttrs.blueBits); @@ -438,7 +438,7 @@ bool GLViewImpl::initWithFullScreen(const std::string& viewName) return false; const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); - return initWithRect(viewName, Rect(0, 0, videoMode->width, videoMode->height), 1.0f); + return initWithRect(viewName, Rect(0, 0, videoMode->width, videoMode->height), 1.0f, false); } bool GLViewImpl::initWithFullscreen(const std::string &viewname, const GLFWvidmode &videoMode, GLFWmonitor *monitor) @@ -454,7 +454,7 @@ bool GLViewImpl::initWithFullscreen(const std::string &viewname, const GLFWvidmo glfwWindowHint(GLFW_BLUE_BITS, videoMode.blueBits); glfwWindowHint(GLFW_GREEN_BITS, videoMode.greenBits); - return initWithRect(viewname, Rect(0, 0, videoMode.width, videoMode.height), 1.0f); + return initWithRect(viewname, Rect(0, 0, videoMode.width, videoMode.height), 1.0f, false); } bool GLViewImpl::isOpenGLReady() @@ -780,11 +780,12 @@ void GLViewImpl::onGLFWframebuffersize(GLFWwindow* window, int w, int h) void GLViewImpl::onGLFWWindowSizeFunCallback(GLFWwindow *window, int width, int height) { - if (_resolutionPolicy != ResolutionPolicy::UNKNOWN) - { - updateDesignResolutionSize(); - Director::getInstance()->setViewport(); - } + int frameWidth = width / _frameZoomFactor; + int frameHeight = height / _frameZoomFactor; + setFrameSize(frameWidth, frameHeight); + + updateDesignResolutionSize(); + Director::getInstance()->setViewport(); } void GLViewImpl::onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified) diff --git a/cocos/platform/desktop/CCGLViewImpl-desktop.h b/cocos/platform/desktop/CCGLViewImpl-desktop.h index 4f87ade941..24061f2767 100644 --- a/cocos/platform/desktop/CCGLViewImpl-desktop.h +++ b/cocos/platform/desktop/CCGLViewImpl-desktop.h @@ -57,7 +57,7 @@ class CC_DLL GLViewImpl : public GLView { public: static GLViewImpl* create(const std::string& viewName); - static GLViewImpl* createWithRect(const std::string& viewName, Rect size, float frameZoomFactor = 1.0f); + static GLViewImpl* createWithRect(const std::string& viewName, Rect size, float frameZoomFactor = 1.0f, bool resizable = false); static GLViewImpl* createWithFullScreen(const std::string& viewName); static GLViewImpl* createWithFullScreen(const std::string& viewName, const GLFWvidmode &videoMode, GLFWmonitor *monitor); @@ -115,7 +115,7 @@ protected: GLViewImpl(bool initglfw = true); virtual ~GLViewImpl(); - bool initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor); + bool initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable); bool initWithFullScreen(const std::string& viewName); bool initWithFullscreen(const std::string& viewname, const GLFWvidmode &videoMode, GLFWmonitor *monitor); diff --git a/cocos/platform/ios/CCEAGLView-ios.mm b/cocos/platform/ios/CCEAGLView-ios.mm index ed1f0a2dde..2f8d04c3a2 100644 --- a/cocos/platform/ios/CCEAGLView-ios.mm +++ b/cocos/platform/ios/CCEAGLView-ios.mm @@ -427,7 +427,7 @@ Copyright (C) 2008 Apple Inc. All Rights Reserved. ids[i] = touch; xs[i] = [touch locationInView: [touch view]].x * self.contentScaleFactor;; ys[i] = [touch locationInView: [touch view]].y * self.contentScaleFactor;; -#ifdef __IPHONE_9_0 && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 +#if defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) // running on iOS 9.0 or higher version if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0f) { fs[i] = touch.force; diff --git a/cocos/platform/win32/CCApplication-win32.cpp b/cocos/platform/win32/CCApplication-win32.cpp index 1c752094ef..e2924e936e 100644 --- a/cocos/platform/win32/CCApplication-win32.cpp +++ b/cocos/platform/win32/CCApplication-win32.cpp @@ -31,6 +31,7 @@ THE SOFTWARE. #include #include "platform/CCFileUtils.h" #include +#include /** @brief This function change the PVRFrame show/hide setting in register. @param bEnable If true show the PVRFrame window, otherwise hide. @@ -219,7 +220,43 @@ Application::Platform Application::getTargetPlatform() std::string Application::getVersion() { - return ""; + char verString[256] = { 0 }; + TCHAR szVersionFile[MAX_PATH]; + GetModuleFileName(NULL, szVersionFile, MAX_PATH); + DWORD verHandle = NULL; + UINT size = 0; + LPBYTE lpBuffer = NULL; + DWORD verSize = GetFileVersionInfoSize(szVersionFile, &verHandle); + + if (verSize != NULL) + { + LPSTR verData = new char[verSize]; + + if (GetFileVersionInfo(szVersionFile, verHandle, verSize, verData)) + { + if (VerQueryValue(verData, L"\\", (VOID FAR* FAR*)&lpBuffer, &size)) + { + if (size) + { + VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)lpBuffer; + if (verInfo->dwSignature == 0xfeef04bd) + { + + // Doesn't matter if you are on 32 bit or 64 bit, + // DWORD is always 32 bits, so first two revision numbers + // come from dwFileVersionMS, last two come from dwFileVersionLS + sprintf(verString, "%d.%d.%d.%d", (verInfo->dwFileVersionMS >> 16) & 0xffff, + (verInfo->dwFileVersionMS >> 0) & 0xffff, + (verInfo->dwFileVersionLS >> 16) & 0xffff, + (verInfo->dwFileVersionLS >> 0) & 0xffff + ); + } + } + } + } + delete[] verData; + } + return verString; } bool Application::openURL(const std::string &url) diff --git a/cocos/platform/winrt/CCFileUtilsWinRT.cpp b/cocos/platform/winrt/CCFileUtilsWinRT.cpp index e6ceec1d90..338f9eba89 100644 --- a/cocos/platform/winrt/CCFileUtilsWinRT.cpp +++ b/cocos/platform/winrt/CCFileUtilsWinRT.cpp @@ -129,6 +129,19 @@ std::string CCFileUtilsWinRT::getSuitableFOpen(const std::string& filenameUtf8) return UTF8StringToMultiByte(filenameUtf8); } +long CCFileUtilsWinRT::getFileSize(const std::string &filepath) +{ + WIN32_FILE_ATTRIBUTE_DATA fad; + if (!GetFileAttributesEx(StringUtf8ToWideChar(filepath).c_str(), GetFileExInfoStandard, &fad)) + { + return 0; // error condition, could call GetLastError to find out more + } + LARGE_INTEGER size; + size.HighPart = fad.nFileSizeHigh; + size.LowPart = fad.nFileSizeLow; + return (long)size.QuadPart; +} + bool CCFileUtilsWinRT::isFileExistInternal(const std::string& strFilePath) const { bool ret = false; diff --git a/cocos/platform/winrt/CCFileUtilsWinRT.h b/cocos/platform/winrt/CCFileUtilsWinRT.h index 3d57b9d2f0..47538d4357 100644 --- a/cocos/platform/winrt/CCFileUtilsWinRT.h +++ b/cocos/platform/winrt/CCFileUtilsWinRT.h @@ -53,6 +53,7 @@ public: virtual std::string getFullPathForDirectoryAndFilename(const std::string& strDirectory, const std::string& strFilename) const override; virtual std::string getStringFromFile(const std::string& filename) override; virtual std::string getSuitableFOpen(const std::string& filenameUtf8) const override; + virtual long getFileSize(const std::string &filepath); static std::string getAppPath(); private: diff --git a/cocos/renderer/CCMaterial.cpp b/cocos/renderer/CCMaterial.cpp index 5b5d1aa8dd..8d8ac7d815 100644 --- a/cocos/renderer/CCMaterial.cpp +++ b/cocos/renderer/CCMaterial.cpp @@ -105,10 +105,6 @@ bool Material::initWithGLProgramState(cocos2d::GLProgramState *state) bool Material::initWithFile(const std::string& validfilename) { - Data data = FileUtils::getInstance()->getDataFromFile(validfilename); - char* bytes = (char*)data.getBytes(); - bytes[data.getSize()-1]='\0'; - // Warning: properties is not a "Ref" object, must be manually deleted Properties* properties = Properties::createNonRefCounted(validfilename); diff --git a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_auto_api.js b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_auto_api.js index af83603bf7..0ec34df40b 100644 --- a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_auto_api.js +++ b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_auto_api.js @@ -6155,6 +6155,16 @@ supportsPVRTC : function ( return false; }, +/** + * @method supportsOESDepth24 + * @return {bool} + */ +supportsOESDepth24 : function ( +) +{ + return false; +}, + /** * @method getMaxModelviewStackDepth * @return {int} @@ -6319,6 +6329,16 @@ supportsDiscardFramebuffer : function ( return false; }, +/** + * @method supportsOESPackedDepthStencil + * @return {bool} + */ +supportsOESPackedDepthStencil : function ( +) +{ + return false; +}, + /** * @method supportsS3TC * @return {bool} @@ -12151,6 +12171,16 @@ getShadowOffset : function ( return cc.Size; }, +/** + * @method getLineSpacing + * @return {float} + */ +getLineSpacing : function ( +) +{ + return 0; +}, + /** * @method setClipMarginEnabled * @param {bool} arg0 @@ -12181,6 +12211,16 @@ str { }, +/** + * @method isWrapEnabled + * @return {bool} + */ +isWrapEnabled : function ( +) +{ + return false; +}, + /** * @method getOutlineSize * @return {int} @@ -12260,13 +12300,11 @@ overflow }, /** - * @method getLineSpacing - * @return {float} + * @method enableStrikethrough */ -getLineSpacing : function ( +enableStrikethrough : function ( ) { - return 0; }, /** @@ -12448,13 +12486,11 @@ getTTFConfig : function ( }, /** - * @method getVerticalAlignment - * @return {cc.TextVAlignment} + * @method enableItalics */ -getVerticalAlignment : function ( +enableItalics : function ( ) { - return 0; }, /** @@ -12519,6 +12555,16 @@ getOverflow : function ( return 0; }, +/** + * @method getVerticalAlignment + * @return {cc.TextVAlignment} + */ +getVerticalAlignment : function ( +) +{ + return 0; +}, + /** * @method setAdditionalKerning * @param {float} arg0 @@ -12580,13 +12626,19 @@ texthalignment }, /** - * @method isWrapEnabled - * @return {bool} + * @method enableBold */ -isWrapEnabled : function ( +enableBold : function ( +) +{ +}, + +/** + * @method enableUnderline + */ +enableUnderline : function ( ) { - return false; }, /** @@ -15363,6 +15415,14 @@ vec2 { }, +/** + * @method stop + */ +stop : function ( +) +{ +}, + /** * @method updateParticleQuads */ @@ -15421,6 +15481,14 @@ bool { }, +/** + * @method start + */ +start : function ( +) +{ +}, + /** * @method setEndSizeVar * @param {float} arg0 diff --git a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_studio_auto_api.js b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_studio_auto_api.js index b36cd47631..ffc2963314 100644 --- a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_studio_auto_api.js +++ b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_studio_auto_api.js @@ -2617,6 +2617,14 @@ end : function ( { }, +/** + * @method start + */ +start : function ( +) +{ +}, + /** * @method stopBackgroundMusic * @param {bool} bool @@ -2693,6 +2701,14 @@ bool { }, +/** + * @method stop + */ +stop : function ( +) +{ +}, + /** * @method playEffect * @param {char|char} char @@ -4241,6 +4257,14 @@ pause : function ( { }, +/** + * @method start + */ +start : function ( +) +{ +}, + /** * @method init * @return {bool} diff --git a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_ui_auto_api.js b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_ui_auto_api.js index 2f3f7959a0..78c6c8fecd 100644 --- a/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_ui_auto_api.js +++ b/cocos/scripting/js-bindings/auto/api/jsb_cocos2dx_ui_auto_api.js @@ -3233,6 +3233,26 @@ jumpToBottomRight : function ( { }, +/** + * @method setTouchTotalTimeThreshold + * @param {float} arg0 + */ +setTouchTotalTimeThreshold : function ( +float +) +{ +}, + +/** + * @method getTouchTotalTimeThreshold + * @return {float} + */ +getTouchTotalTimeThreshold : function ( +) +{ + return 0; +}, + /** * @method getScrollBarPositionFromCornerForHorizontal * @return {vec2_object} @@ -5314,6 +5334,8 @@ ccui.RichElementText = { * @param {String} arg3 * @param {String} arg4 * @param {float} arg5 + * @param {unsigned int} arg6 + * @param {String} arg7 * @return {bool} */ init : function ( @@ -5322,7 +5344,9 @@ color3b, char, str, str, -float +float, +int, +str ) { return false; @@ -5336,6 +5360,8 @@ float * @param {String} arg3 * @param {String} arg4 * @param {float} arg5 + * @param {unsigned int} arg6 + * @param {String} arg7 * @return {ccui.RichElementText} */ create : function ( @@ -5344,7 +5370,9 @@ color3b, char, str, str, -float +float, +int, +str ) { return ccui.RichElementText; @@ -5366,6 +5394,16 @@ RichElementText : function ( */ ccui.RichElementImage = { +/** + * @method setHeight + * @param {int} arg0 + */ +setHeight : function ( +int +) +{ +}, + /** * @method init * @param {int} arg0 @@ -5384,6 +5422,16 @@ str return false; }, +/** + * @method setWidth + * @param {int} arg0 + */ +setWidth : function ( +int +) +{ +}, + /** * @method create * @param {int} arg0 @@ -5492,6 +5540,16 @@ richelement { }, +/** + * @method setWrapMode + * @param {ccui.RichText::WrapMode} arg0 + */ +setWrapMode : function ( +wrapmode +) +{ +}, + /** * @method setVerticalSpace * @param {float} arg0 @@ -5502,6 +5560,16 @@ float { }, +/** + * @method getWrapMode + * @return {ccui.RichText::WrapMode} + */ +getWrapMode : function ( +) +{ + return 0; +}, + /** * @method formatText */ @@ -5510,6 +5578,18 @@ formatText : function ( { }, +/** + * @method initWithXML + * @param {String} arg0 + * @return {bool} + */ +initWithXML : function ( +str +) +{ + return false; +}, + /** * @method removeElement * @param {ccui.RichElement|int} richelement @@ -5530,6 +5610,18 @@ create : function ( return ccui.RichText; }, +/** + * @method createWithXML + * @param {String} arg0 + * @return {ccui.RichText} + */ +createWithXML : function ( +str +) +{ + return ccui.RichText; +}, + /** * @method RichText * @constructor diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.cpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.cpp index ce31d32d27..05d2858ceb 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.cpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.cpp @@ -16452,6 +16452,24 @@ bool js_cocos2dx_Configuration_supportsPVRTC(JSContext *cx, uint32_t argc, jsval JS_ReportError(cx, "js_cocos2dx_Configuration_supportsPVRTC : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_Configuration_supportsOESDepth24(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Configuration* cobj = (cocos2d::Configuration *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Configuration_supportsOESDepth24 : Invalid Native Object"); + if (argc == 0) { + bool ret = cobj->supportsOESDepth24(); + jsval jsret = JSVAL_NULL; + jsret = BOOLEAN_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Configuration_supportsOESDepth24 : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_Configuration_getMaxModelviewStackDepth(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -16750,6 +16768,24 @@ bool js_cocos2dx_Configuration_supportsDiscardFramebuffer(JSContext *cx, uint32_ JS_ReportError(cx, "js_cocos2dx_Configuration_supportsDiscardFramebuffer : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_Configuration_supportsOESPackedDepthStencil(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Configuration* cobj = (cocos2d::Configuration *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Configuration_supportsOESPackedDepthStencil : Invalid Native Object"); + if (argc == 0) { + bool ret = cobj->supportsOESPackedDepthStencil(); + jsval jsret = JSVAL_NULL; + jsret = BOOLEAN_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Configuration_supportsOESPackedDepthStencil : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_Configuration_supportsS3TC(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -16902,6 +16938,7 @@ void js_register_cocos2dx_Configuration(JSContext *cx, JS::HandleObject global) static JSFunctionSpec funcs[] = { JS_FN("supportsPVRTC", js_cocos2dx_Configuration_supportsPVRTC, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("supportsOESDepth24", js_cocos2dx_Configuration_supportsOESDepth24, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getMaxModelviewStackDepth", js_cocos2dx_Configuration_getMaxModelviewStackDepth, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("supportsShareableVAO", js_cocos2dx_Configuration_supportsShareableVAO, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("supportsBGRA8888", js_cocos2dx_Configuration_supportsBGRA8888, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -16918,6 +16955,7 @@ void js_register_cocos2dx_Configuration(JSContext *cx, JS::HandleObject global) JS_FN("getMaxSupportDirLightInShader", js_cocos2dx_Configuration_getMaxSupportDirLightInShader, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("loadConfigFile", js_cocos2dx_Configuration_loadConfigFile, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("supportsDiscardFramebuffer", js_cocos2dx_Configuration_supportsDiscardFramebuffer, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("supportsOESPackedDepthStencil", js_cocos2dx_Configuration_supportsOESPackedDepthStencil, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("supportsS3TC", js_cocos2dx_Configuration_supportsS3TC, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("dumpInfo", js_cocos2dx_Configuration_getInfo, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getMaxTextureUnits", js_cocos2dx_Configuration_getMaxTextureUnits, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -33144,6 +33182,24 @@ bool js_cocos2dx_Label_getShadowOffset(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_Label_getShadowOffset : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_Label_getLineSpacing(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_getLineSpacing : Invalid Native Object"); + if (argc == 0) { + double ret = cobj->getLineSpacing(); + jsval jsret = JSVAL_NULL; + jsret = DOUBLE_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Label_getLineSpacing : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_Label_setClipMarginEnabled(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -33204,6 +33260,24 @@ bool js_cocos2dx_Label_setSystemFontName(JSContext *cx, uint32_t argc, jsval *vp JS_ReportError(cx, "js_cocos2dx_Label_setSystemFontName : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } +bool js_cocos2dx_Label_isWrapEnabled(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_isWrapEnabled : Invalid Native Object"); + if (argc == 0) { + bool ret = cobj->isWrapEnabled(); + jsval jsret = JSVAL_NULL; + jsret = BOOLEAN_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Label_isWrapEnabled : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_Label_getOutlineSize(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -33498,22 +33572,20 @@ bool js_cocos2dx_Label_setOverflow(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_Label_setOverflow : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } -bool js_cocos2dx_Label_getLineSpacing(JSContext *cx, uint32_t argc, jsval *vp) +bool js_cocos2dx_Label_enableStrikethrough(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); js_proxy_t *proxy = jsb_get_js_proxy(obj); cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); - JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_getLineSpacing : Invalid Native Object"); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_enableStrikethrough : Invalid Native Object"); if (argc == 0) { - double ret = cobj->getLineSpacing(); - jsval jsret = JSVAL_NULL; - jsret = DOUBLE_TO_JSVAL(ret); - args.rval().set(jsret); + cobj->enableStrikethrough(); + args.rval().setUndefined(); return true; } - JS_ReportError(cx, "js_cocos2dx_Label_getLineSpacing : wrong number of arguments: %d, was expecting %d", argc, 0); + JS_ReportError(cx, "js_cocos2dx_Label_enableStrikethrough : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } bool js_cocos2dx_Label_updateContent(JSContext *cx, uint32_t argc, jsval *vp) @@ -33903,22 +33975,20 @@ bool js_cocos2dx_Label_getTTFConfig(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_Label_getTTFConfig : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } -bool js_cocos2dx_Label_getVerticalAlignment(JSContext *cx, uint32_t argc, jsval *vp) +bool js_cocos2dx_Label_enableItalics(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); js_proxy_t *proxy = jsb_get_js_proxy(obj); cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); - JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_getVerticalAlignment : Invalid Native Object"); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_enableItalics : Invalid Native Object"); if (argc == 0) { - int ret = (int)cobj->getVerticalAlignment(); - jsval jsret = JSVAL_NULL; - jsret = int32_to_jsval(cx, ret); - args.rval().set(jsret); + cobj->enableItalics(); + args.rval().setUndefined(); return true; } - JS_ReportError(cx, "js_cocos2dx_Label_getVerticalAlignment : wrong number of arguments: %d, was expecting %d", argc, 0); + JS_ReportError(cx, "js_cocos2dx_Label_enableItalics : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } bool js_cocos2dx_Label_setTextColor(JSContext *cx, uint32_t argc, jsval *vp) @@ -34043,6 +34113,24 @@ bool js_cocos2dx_Label_getOverflow(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_Label_getOverflow : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_Label_getVerticalAlignment(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_getVerticalAlignment : Invalid Native Object"); + if (argc == 0) { + int ret = (int)cobj->getVerticalAlignment(); + jsval jsret = JSVAL_NULL; + jsret = int32_to_jsval(cx, ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Label_getVerticalAlignment : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_Label_setAdditionalKerning(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -34157,22 +34245,36 @@ bool js_cocos2dx_Label_setHorizontalAlignment(JSContext *cx, uint32_t argc, jsva JS_ReportError(cx, "js_cocos2dx_Label_setHorizontalAlignment : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } -bool js_cocos2dx_Label_isWrapEnabled(JSContext *cx, uint32_t argc, jsval *vp) +bool js_cocos2dx_Label_enableBold(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); js_proxy_t *proxy = jsb_get_js_proxy(obj); cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); - JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_isWrapEnabled : Invalid Native Object"); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_enableBold : Invalid Native Object"); if (argc == 0) { - bool ret = cobj->isWrapEnabled(); - jsval jsret = JSVAL_NULL; - jsret = BOOLEAN_TO_JSVAL(ret); - args.rval().set(jsret); + cobj->enableBold(); + args.rval().setUndefined(); return true; } - JS_ReportError(cx, "js_cocos2dx_Label_isWrapEnabled : wrong number of arguments: %d, was expecting %d", argc, 0); + JS_ReportError(cx, "js_cocos2dx_Label_enableBold : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} +bool js_cocos2dx_Label_enableUnderline(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::Label* cobj = (cocos2d::Label *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Label_enableUnderline : Invalid Native Object"); + if (argc == 0) { + cobj->enableUnderline(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_Label_enableUnderline : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } bool js_cocos2dx_Label_getLabelEffectType(JSContext *cx, uint32_t argc, jsval *vp) @@ -34587,16 +34689,18 @@ void js_register_cocos2dx_Label(JSContext *cx, JS::HandleObject global) { JS_FN("getMaxLineWidth", js_cocos2dx_Label_getMaxLineWidth, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getHorizontalAlignment", js_cocos2dx_Label_getHorizontalAlignment, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getShadowOffset", js_cocos2dx_Label_getShadowOffset, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("getLineSpacing", js_cocos2dx_Label_getLineSpacing, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setClipMarginEnabled", js_cocos2dx_Label_setClipMarginEnabled, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setString", js_cocos2dx_Label_setString, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setSystemFontName", js_cocos2dx_Label_setSystemFontName, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("isWrapEnabled", js_cocos2dx_Label_isWrapEnabled, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getOutlineSize", js_cocos2dx_Label_getOutlineSize, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBMFontFilePath", js_cocos2dx_Label_setBMFontFilePath, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("initWithTTF", js_cocos2dx_Label_initWithTTF, 2, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setLineHeight", js_cocos2dx_Label_setLineHeight, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setSystemFontSize", js_cocos2dx_Label_setSystemFontSize, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setOverflow", js_cocos2dx_Label_setOverflow, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), - JS_FN("getLineSpacing", js_cocos2dx_Label_getLineSpacing, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("enableStrikethrough", js_cocos2dx_Label_enableStrikethrough, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("updateContent", js_cocos2dx_Label_updateContent, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getStringLength", js_cocos2dx_Label_getStringLength, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setLineBreakWithoutSpace", js_cocos2dx_Label_setLineBreakWithoutSpace, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -34614,20 +34718,22 @@ void js_register_cocos2dx_Label(JSContext *cx, JS::HandleObject global) { JS_FN("getLineHeight", js_cocos2dx_Label_getLineHeight, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getShadowColor", js_cocos2dx_Label_getShadowColor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getTTFConfig", js_cocos2dx_Label_getTTFConfig, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), - JS_FN("getVerticalAlignment", js_cocos2dx_Label_getVerticalAlignment, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("enableItalics", js_cocos2dx_Label_enableItalics, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setTextColor", js_cocos2dx_Label_setTextColor, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getLetter", js_cocos2dx_Label_getLetter, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setHeight", js_cocos2dx_Label_setHeight, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("isShadowEnabled", js_cocos2dx_Label_isShadowEnabled, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("enableGlow", js_cocos2dx_Label_enableGlow, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getOverflow", js_cocos2dx_Label_getOverflow, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("getVerticalAlignment", js_cocos2dx_Label_getVerticalAlignment, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setAdditionalKerning", js_cocos2dx_Label_setAdditionalKerning, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getSystemFontSize", js_cocos2dx_Label_getSystemFontSize, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBlendFunc", js_cocos2dx_Label_setBlendFunc, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getTextAlignment", js_cocos2dx_Label_getTextAlignment, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getBMFontFilePath", js_cocos2dx_Label_getBMFontFilePath, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setHorizontalAlignment", js_cocos2dx_Label_setHorizontalAlignment, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), - JS_FN("isWrapEnabled", js_cocos2dx_Label_isWrapEnabled, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("enableBold", js_cocos2dx_Label_enableBold, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("enableUnderline", js_cocos2dx_Label_enableUnderline, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getLabelEffectType", js_cocos2dx_Label_getLabelEffectType, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setAlignment", js_cocos2dx_Label_setAlignment, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("requestSystemFontRefresh", js_cocos2dx_Label_requestSystemFontRefresh, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -41747,6 +41853,22 @@ bool js_cocos2dx_ParticleSystem_setSourcePosition(JSContext *cx, uint32_t argc, JS_ReportError(cx, "js_cocos2dx_ParticleSystem_setSourcePosition : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } +bool js_cocos2dx_ParticleSystem_stop(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ParticleSystem* cobj = (cocos2d::ParticleSystem *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ParticleSystem_stop : Invalid Native Object"); + if (argc == 0) { + cobj->stop(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ParticleSystem_stop : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_ParticleSystem_updateParticleQuads(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -41861,6 +41983,22 @@ bool js_cocos2dx_ParticleSystem_setRotationIsDir(JSContext *cx, uint32_t argc, j JS_ReportError(cx, "js_cocos2dx_ParticleSystem_setRotationIsDir : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } +bool js_cocos2dx_ParticleSystem_start(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ParticleSystem* cobj = (cocos2d::ParticleSystem *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ParticleSystem_start : Invalid Native Object"); + if (argc == 0) { + cobj->start(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ParticleSystem_start : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_ParticleSystem_setEndSizeVar(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -42738,12 +42876,14 @@ void js_register_cocos2dx_ParticleSystem(JSContext *cx, JS::HandleObject global) JS_FN("setEmitterMode", js_cocos2dx_ParticleSystem_setEmitterMode, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getDuration", js_cocos2dx_ParticleSystem_getDuration, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setSourcePosition", js_cocos2dx_ParticleSystem_setSourcePosition, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("stop", js_cocos2dx_ParticleSystem_stop, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("updateParticleQuads", js_cocos2dx_ParticleSystem_updateParticleQuads, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getEndSpinVar", js_cocos2dx_ParticleSystem_getEndSpinVar, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBlendAdditive", js_cocos2dx_ParticleSystem_setBlendAdditive, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setLife", js_cocos2dx_ParticleSystem_setLife, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setAngleVar", js_cocos2dx_ParticleSystem_setAngleVar, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setRotationIsDir", js_cocos2dx_ParticleSystem_setRotationIsDir, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("start", js_cocos2dx_ParticleSystem_start, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setEndSizeVar", js_cocos2dx_ParticleSystem_setEndSizeVar, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setAngle", js_cocos2dx_ParticleSystem_setAngle, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBatchNode", js_cocos2dx_ParticleSystem_setBatchNode, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.hpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.hpp index cd4f07419a..9494ae9d67 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.hpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_auto.hpp @@ -950,6 +950,7 @@ void js_cocos2dx_Configuration_finalize(JSContext *cx, JSObject *obj); void js_register_cocos2dx_Configuration(JSContext *cx, JS::HandleObject global); void register_all_cocos2dx(JSContext* cx, JS::HandleObject obj); bool js_cocos2dx_Configuration_supportsPVRTC(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Configuration_supportsOESDepth24(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_getMaxModelviewStackDepth(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_supportsShareableVAO(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_supportsBGRA8888(JSContext *cx, uint32_t argc, jsval *vp); @@ -966,6 +967,7 @@ bool js_cocos2dx_Configuration_supportsETC(JSContext *cx, uint32_t argc, jsval * bool js_cocos2dx_Configuration_getMaxSupportDirLightInShader(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_loadConfigFile(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_supportsDiscardFramebuffer(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Configuration_supportsOESPackedDepthStencil(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_supportsS3TC(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_getInfo(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Configuration_getMaxTextureUnits(JSContext *cx, uint32_t argc, jsval *vp); @@ -2239,16 +2241,18 @@ bool js_cocos2dx_Label_getBMFontSize(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getMaxLineWidth(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getHorizontalAlignment(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getShadowOffset(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_getLineSpacing(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setClipMarginEnabled(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setString(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setSystemFontName(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_isWrapEnabled(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getOutlineSize(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setBMFontFilePath(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_initWithTTF(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setLineHeight(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setSystemFontSize(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setOverflow(JSContext *cx, uint32_t argc, jsval *vp); -bool js_cocos2dx_Label_getLineSpacing(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_enableStrikethrough(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_updateContent(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getStringLength(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setLineBreakWithoutSpace(JSContext *cx, uint32_t argc, jsval *vp); @@ -2266,20 +2270,22 @@ bool js_cocos2dx_Label_setLineSpacing(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getLineHeight(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getShadowColor(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getTTFConfig(JSContext *cx, uint32_t argc, jsval *vp); -bool js_cocos2dx_Label_getVerticalAlignment(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_enableItalics(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setTextColor(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getLetter(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setHeight(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_isShadowEnabled(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_enableGlow(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getOverflow(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_getVerticalAlignment(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setAdditionalKerning(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getSystemFontSize(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setBlendFunc(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getTextAlignment(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getBMFontFilePath(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setHorizontalAlignment(JSContext *cx, uint32_t argc, jsval *vp); -bool js_cocos2dx_Label_isWrapEnabled(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_enableBold(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_Label_enableUnderline(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_getLabelEffectType(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_setAlignment(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_Label_requestSystemFontRefresh(JSContext *cx, uint32_t argc, jsval *vp); @@ -2692,12 +2698,14 @@ bool js_cocos2dx_ParticleSystem_getRotatePerSecond(JSContext *cx, uint32_t argc, bool js_cocos2dx_ParticleSystem_setEmitterMode(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_getDuration(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setSourcePosition(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ParticleSystem_stop(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_updateParticleQuads(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_getEndSpinVar(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setBlendAdditive(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setLife(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setAngleVar(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setRotationIsDir(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ParticleSystem_start(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setEndSizeVar(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setAngle(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ParticleSystem_setBatchNode(JSContext *cx, uint32_t argc, jsval *vp); diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.cpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.cpp index f26ef41787..ffc9df5ec9 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.cpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.cpp @@ -6663,6 +6663,22 @@ bool js_cocos2dx_studio_ComAudio_end(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_studio_ComAudio_end : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_studio_ComAudio_start(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocostudio::ComAudio* cobj = (cocostudio::ComAudio *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_studio_ComAudio_start : Invalid Native Object"); + if (argc == 0) { + cobj->start(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_studio_ComAudio_start : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_studio_ComAudio_stopBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp) { bool ok = true; @@ -6845,6 +6861,22 @@ bool js_cocos2dx_studio_ComAudio_playBackgroundMusic(JSContext *cx, uint32_t arg JS_ReportError(cx, "js_cocos2dx_studio_ComAudio_playBackgroundMusic : wrong number of arguments"); return false; } +bool js_cocos2dx_studio_ComAudio_stop(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocostudio::ComAudio* cobj = (cocostudio::ComAudio *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_studio_ComAudio_stop : Invalid Native Object"); + if (argc == 0) { + cobj->stop(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_studio_ComAudio_stop : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_studio_ComAudio_playEffect(JSContext *cx, uint32_t argc, jsval *vp) { bool ok = true; @@ -7147,6 +7179,7 @@ void js_register_cocos2dx_studio_ComAudio(JSContext *cx, JS::HandleObject global JS_FN("willPlayBackgroundMusic", js_cocos2dx_studio_ComAudio_willPlayBackgroundMusic, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBackgroundMusicVolume", js_cocos2dx_studio_ComAudio_setBackgroundMusicVolume, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("end", js_cocos2dx_studio_ComAudio_end, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("start", js_cocos2dx_studio_ComAudio_start, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("stopBackgroundMusic", js_cocos2dx_studio_ComAudio_stopBackgroundMusic, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("pauseBackgroundMusic", js_cocos2dx_studio_ComAudio_pauseBackgroundMusic, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("isBackgroundMusicPlaying", js_cocos2dx_studio_ComAudio_isBackgroundMusicPlaying, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -7155,6 +7188,7 @@ void js_register_cocos2dx_studio_ComAudio(JSContext *cx, JS::HandleObject global JS_FN("pauseAllEffects", js_cocos2dx_studio_ComAudio_pauseAllEffects, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("preloadBackgroundMusic", js_cocos2dx_studio_ComAudio_preloadBackgroundMusic, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("playBackgroundMusic", js_cocos2dx_studio_ComAudio_playBackgroundMusic, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("stop", js_cocos2dx_studio_ComAudio_stop, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("playEffect", js_cocos2dx_studio_ComAudio_playEffect, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("preloadEffect", js_cocos2dx_studio_ComAudio_preloadEffect, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setLoop", js_cocos2dx_studio_ComAudio_setLoop, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -10794,6 +10828,22 @@ bool js_cocos2dx_studio_ActionTimeline_pause(JSContext *cx, uint32_t argc, jsval JS_ReportError(cx, "js_cocos2dx_studio_ActionTimeline_pause : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_studio_ActionTimeline_start(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocostudio::timeline::ActionTimeline* cobj = (cocostudio::timeline::ActionTimeline *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_studio_ActionTimeline_start : Invalid Native Object"); + if (argc == 0) { + cobj->start(); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_studio_ActionTimeline_start : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_studio_ActionTimeline_init(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -11423,6 +11473,7 @@ void js_register_cocos2dx_studio_ActionTimeline(JSContext *cx, JS::HandleObject JS_FN("getCurrentFrame", js_cocos2dx_studio_ActionTimeline_getCurrentFrame, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getStartFrame", js_cocos2dx_studio_ActionTimeline_getStartFrame, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("pause", js_cocos2dx_studio_ActionTimeline_pause, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("start", js_cocos2dx_studio_ActionTimeline_start, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("init", js_cocos2dx_studio_ActionTimeline_init, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("removeTimeline", js_cocos2dx_studio_ActionTimeline_removeTimeline, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setLastFrameCallFunc", js_cocos2dx_studio_ActionTimeline_setLastFrameCallFunc, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.hpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.hpp index 51c7d8ba4b..e00a627858 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.hpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_studio_auto.hpp @@ -413,6 +413,7 @@ bool js_cocos2dx_studio_ComAudio_getBackgroundMusicVolume(JSContext *cx, uint32_ bool js_cocos2dx_studio_ComAudio_willPlayBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_setBackgroundMusicVolume(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_end(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_studio_ComAudio_start(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_stopBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_pauseBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_isBackgroundMusicPlaying(JSContext *cx, uint32_t argc, jsval *vp); @@ -421,6 +422,7 @@ bool js_cocos2dx_studio_ComAudio_resumeAllEffects(JSContext *cx, uint32_t argc, bool js_cocos2dx_studio_ComAudio_pauseAllEffects(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_preloadBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_playBackgroundMusic(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_studio_ComAudio_stop(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_playEffect(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_preloadEffect(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ComAudio_setLoop(JSContext *cx, uint32_t argc, jsval *vp); @@ -730,6 +732,7 @@ bool js_cocos2dx_studio_ActionTimeline_addTimeline(JSContext *cx, uint32_t argc, bool js_cocos2dx_studio_ActionTimeline_getCurrentFrame(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ActionTimeline_getStartFrame(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ActionTimeline_pause(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_studio_ActionTimeline_start(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ActionTimeline_init(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ActionTimeline_removeTimeline(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_studio_ActionTimeline_setLastFrameCallFunc(JSContext *cx, uint32_t argc, jsval *vp); diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.cpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.cpp index f33a2599ba..aeb3da167f 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.cpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.cpp @@ -7981,6 +7981,44 @@ bool js_cocos2dx_ui_ScrollView_jumpToBottomRight(JSContext *cx, uint32_t argc, j JS_ReportError(cx, "js_cocos2dx_ui_ScrollView_jumpToBottomRight : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::ScrollView* cobj = (cocos2d::ui::ScrollView *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold : Invalid Native Object"); + if (argc == 1) { + double arg0 = 0; + ok &= JS::ToNumber( cx, args.get(0), &arg0) && !isnan(arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold : Error processing arguments"); + cobj->setTouchTotalTimeThreshold(arg0); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold : wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +bool js_cocos2dx_ui_ScrollView_getTouchTotalTimeThreshold(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::ScrollView* cobj = (cocos2d::ui::ScrollView *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_ScrollView_getTouchTotalTimeThreshold : Invalid Native Object"); + if (argc == 0) { + double ret = cobj->getTouchTotalTimeThreshold(); + jsval jsret = JSVAL_NULL; + jsret = DOUBLE_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_ScrollView_getTouchTotalTimeThreshold : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_ui_ScrollView_getScrollBarPositionFromCornerForHorizontal(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -8486,6 +8524,8 @@ void js_register_cocos2dx_ui_ScrollView(JSContext *cx, JS::HandleObject global) JS_FN("jumpToTopLeft", js_cocos2dx_ui_ScrollView_jumpToTopLeft, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("jumpToPercentHorizontal", js_cocos2dx_ui_ScrollView_jumpToPercentHorizontal, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("jumpToBottomRight", js_cocos2dx_ui_ScrollView_jumpToBottomRight, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("setTouchTotalTimeThreshold", js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("getTouchTotalTimeThreshold", js_cocos2dx_ui_ScrollView_getTouchTotalTimeThreshold, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("getScrollBarPositionFromCornerForHorizontal", js_cocos2dx_ui_ScrollView_getScrollBarPositionFromCornerForHorizontal, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setScrollBarWidth", js_cocos2dx_ui_ScrollView_setScrollBarWidth, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setBounceEnabled", js_cocos2dx_ui_ScrollView_setBounceEnabled, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -12908,28 +12948,32 @@ bool js_cocos2dx_ui_RichElementText_init(JSContext *cx, uint32_t argc, jsval *vp js_proxy_t *proxy = jsb_get_js_proxy(obj); cocos2d::ui::RichElementText* cobj = (cocos2d::ui::RichElementText *)(proxy ? proxy->ptr : NULL); JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichElementText_init : Invalid Native Object"); - if (argc == 6) { + if (argc == 8) { int arg0 = 0; cocos2d::Color3B arg1; uint16_t arg2; std::string arg3; std::string arg4; double arg5 = 0; + unsigned int arg6 = 0; + std::string arg7; ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); ok &= jsval_to_cccolor3b(cx, args.get(1), &arg1); ok &= jsval_to_uint16(cx, args.get(2), &arg2); ok &= jsval_to_std_string(cx, args.get(3), &arg3); ok &= jsval_to_std_string(cx, args.get(4), &arg4); ok &= JS::ToNumber( cx, args.get(5), &arg5) && !isnan(arg5); + ok &= jsval_to_uint32(cx, args.get(6), &arg6); + ok &= jsval_to_std_string(cx, args.get(7), &arg7); JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichElementText_init : Error processing arguments"); - bool ret = cobj->init(arg0, arg1, arg2, arg3, arg4, arg5); + bool ret = cobj->init(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); jsval jsret = JSVAL_NULL; jsret = BOOLEAN_TO_JSVAL(ret); args.rval().set(jsret); return true; } - JS_ReportError(cx, "js_cocos2dx_ui_RichElementText_init : wrong number of arguments: %d, was expecting %d", argc, 6); + JS_ReportError(cx, "js_cocos2dx_ui_RichElementText_init : wrong number of arguments: %d, was expecting %d", argc, 8); return false; } bool js_cocos2dx_ui_RichElementText_create(JSContext *cx, uint32_t argc, jsval *vp) @@ -12957,6 +13001,54 @@ bool js_cocos2dx_ui_RichElementText_create(JSContext *cx, uint32_t argc, jsval * args.rval().set(OBJECT_TO_JSVAL(jsret)); return true; } + if (argc == 7) { + int arg0 = 0; + cocos2d::Color3B arg1; + uint16_t arg2; + std::string arg3; + std::string arg4; + double arg5 = 0; + unsigned int arg6 = 0; + ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); + ok &= jsval_to_cccolor3b(cx, args.get(1), &arg1); + ok &= jsval_to_uint16(cx, args.get(2), &arg2); + ok &= jsval_to_std_string(cx, args.get(3), &arg3); + ok &= jsval_to_std_string(cx, args.get(4), &arg4); + ok &= JS::ToNumber( cx, args.get(5), &arg5) && !isnan(arg5); + ok &= jsval_to_uint32(cx, args.get(6), &arg6); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichElementText_create : Error processing arguments"); + + auto ret = cocos2d::ui::RichElementText::create(arg0, arg1, arg2, arg3, arg4, arg5, arg6); + js_type_class_t *typeClass = js_get_type_from_native(ret); + JS::RootedObject jsret(cx, jsb_ref_autoreleased_create_jsobject(cx, ret, typeClass, "cocos2d::ui::RichElementText")); + args.rval().set(OBJECT_TO_JSVAL(jsret)); + return true; + } + if (argc == 8) { + int arg0 = 0; + cocos2d::Color3B arg1; + uint16_t arg2; + std::string arg3; + std::string arg4; + double arg5 = 0; + unsigned int arg6 = 0; + std::string arg7; + ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); + ok &= jsval_to_cccolor3b(cx, args.get(1), &arg1); + ok &= jsval_to_uint16(cx, args.get(2), &arg2); + ok &= jsval_to_std_string(cx, args.get(3), &arg3); + ok &= jsval_to_std_string(cx, args.get(4), &arg4); + ok &= JS::ToNumber( cx, args.get(5), &arg5) && !isnan(arg5); + ok &= jsval_to_uint32(cx, args.get(6), &arg6); + ok &= jsval_to_std_string(cx, args.get(7), &arg7); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichElementText_create : Error processing arguments"); + + auto ret = cocos2d::ui::RichElementText::create(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + js_type_class_t *typeClass = js_get_type_from_native(ret); + JS::RootedObject jsret(cx, jsb_ref_autoreleased_create_jsobject(cx, ret, typeClass, "cocos2d::ui::RichElementText")); + args.rval().set(OBJECT_TO_JSVAL(jsret)); + return true; + } JS_ReportError(cx, "js_cocos2dx_ui_RichElementText_create : wrong number of arguments"); return false; } @@ -13013,7 +13105,7 @@ void js_register_cocos2dx_ui_RichElementText(JSContext *cx, JS::HandleObject glo }; static JSFunctionSpec funcs[] = { - JS_FN("init", js_cocos2dx_ui_RichElementText_init, 6, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("init", js_cocos2dx_ui_RichElementText_init, 8, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("ctor", js_cocos2dx_ui_RichElementText_ctor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; @@ -13043,6 +13135,26 @@ void js_register_cocos2dx_ui_RichElementText(JSContext *cx, JS::HandleObject glo JSClass *jsb_cocos2d_ui_RichElementImage_class; JSObject *jsb_cocos2d_ui_RichElementImage_prototype; +bool js_cocos2dx_ui_RichElementImage_setHeight(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::RichElementImage* cobj = (cocos2d::ui::RichElementImage *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichElementImage_setHeight : Invalid Native Object"); + if (argc == 1) { + int arg0 = 0; + ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichElementImage_setHeight : Error processing arguments"); + cobj->setHeight(arg0); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_RichElementImage_setHeight : wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} bool js_cocos2dx_ui_RichElementImage_init(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -13071,6 +13183,26 @@ bool js_cocos2dx_ui_RichElementImage_init(JSContext *cx, uint32_t argc, jsval *v JS_ReportError(cx, "js_cocos2dx_ui_RichElementImage_init : wrong number of arguments: %d, was expecting %d", argc, 4); return false; } +bool js_cocos2dx_ui_RichElementImage_setWidth(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::RichElementImage* cobj = (cocos2d::ui::RichElementImage *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichElementImage_setWidth : Invalid Native Object"); + if (argc == 1) { + int arg0 = 0; + ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichElementImage_setWidth : Error processing arguments"); + cobj->setWidth(arg0); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_RichElementImage_setWidth : wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} bool js_cocos2dx_ui_RichElementImage_create(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -13148,7 +13280,9 @@ void js_register_cocos2dx_ui_RichElementImage(JSContext *cx, JS::HandleObject gl }; static JSFunctionSpec funcs[] = { + JS_FN("setHeight", js_cocos2dx_ui_RichElementImage_setHeight, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("init", js_cocos2dx_ui_RichElementImage_init, 4, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("setWidth", js_cocos2dx_ui_RichElementImage_setWidth, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("ctor", js_cocos2dx_ui_RichElementImage_ctor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; @@ -13387,6 +13521,26 @@ bool js_cocos2dx_ui_RichText_pushBackElement(JSContext *cx, uint32_t argc, jsval JS_ReportError(cx, "js_cocos2dx_ui_RichText_pushBackElement : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } +bool js_cocos2dx_ui_RichText_setWrapMode(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::RichText* cobj = (cocos2d::ui::RichText *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichText_setWrapMode : Invalid Native Object"); + if (argc == 1) { + cocos2d::ui::RichText::WrapMode arg0; + ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichText_setWrapMode : Error processing arguments"); + cobj->setWrapMode(arg0); + args.rval().setUndefined(); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_RichText_setWrapMode : wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} bool js_cocos2dx_ui_RichText_setVerticalSpace(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -13407,6 +13561,24 @@ bool js_cocos2dx_ui_RichText_setVerticalSpace(JSContext *cx, uint32_t argc, jsva JS_ReportError(cx, "js_cocos2dx_ui_RichText_setVerticalSpace : wrong number of arguments: %d, was expecting %d", argc, 1); return false; } +bool js_cocos2dx_ui_RichText_getWrapMode(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::RichText* cobj = (cocos2d::ui::RichText *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichText_getWrapMode : Invalid Native Object"); + if (argc == 0) { + int ret = (int)cobj->getWrapMode(); + jsval jsret = JSVAL_NULL; + jsret = int32_to_jsval(cx, ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_RichText_getWrapMode : wrong number of arguments: %d, was expecting %d", argc, 0); + return false; +} bool js_cocos2dx_ui_RichText_formatText(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -13423,6 +13595,28 @@ bool js_cocos2dx_ui_RichText_formatText(JSContext *cx, uint32_t argc, jsval *vp) JS_ReportError(cx, "js_cocos2dx_ui_RichText_formatText : wrong number of arguments: %d, was expecting %d", argc, 0); return false; } +bool js_cocos2dx_ui_RichText_initWithXML(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + JS::RootedObject obj(cx, args.thisv().toObjectOrNull()); + js_proxy_t *proxy = jsb_get_js_proxy(obj); + cocos2d::ui::RichText* cobj = (cocos2d::ui::RichText *)(proxy ? proxy->ptr : NULL); + JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_ui_RichText_initWithXML : Invalid Native Object"); + if (argc == 1) { + std::string arg0; + ok &= jsval_to_std_string(cx, args.get(0), &arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichText_initWithXML : Error processing arguments"); + bool ret = cobj->initWithXML(arg0); + jsval jsret = JSVAL_NULL; + jsret = BOOLEAN_TO_JSVAL(ret); + args.rval().set(jsret); + return true; + } + + JS_ReportError(cx, "js_cocos2dx_ui_RichText_initWithXML : wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} bool js_cocos2dx_ui_RichText_removeElement(JSContext *cx, uint32_t argc, jsval *vp) { bool ok = true; @@ -13482,6 +13676,25 @@ bool js_cocos2dx_ui_RichText_create(JSContext *cx, uint32_t argc, jsval *vp) return false; } +bool js_cocos2dx_ui_RichText_createWithXML(JSContext *cx, uint32_t argc, jsval *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ok = true; + if (argc == 1) { + std::string arg0; + ok &= jsval_to_std_string(cx, args.get(0), &arg0); + JSB_PRECONDITION2(ok, cx, false, "js_cocos2dx_ui_RichText_createWithXML : Error processing arguments"); + + auto ret = cocos2d::ui::RichText::createWithXML(arg0); + js_type_class_t *typeClass = js_get_type_from_native(ret); + JS::RootedObject jsret(cx, jsb_ref_autoreleased_create_jsobject(cx, ret, typeClass, "cocos2d::ui::RichText")); + args.rval().set(OBJECT_TO_JSVAL(jsret)); + return true; + } + JS_ReportError(cx, "js_cocos2dx_ui_RichText_createWithXML : wrong number of arguments"); + return false; +} + bool js_cocos2dx_ui_RichText_constructor(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -13536,8 +13749,11 @@ void js_register_cocos2dx_ui_RichText(JSContext *cx, JS::HandleObject global) { static JSFunctionSpec funcs[] = { JS_FN("insertElement", js_cocos2dx_ui_RichText_insertElement, 2, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("pushBackElement", js_cocos2dx_ui_RichText_pushBackElement, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("setWrapMode", js_cocos2dx_ui_RichText_setWrapMode, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("setVerticalSpace", js_cocos2dx_ui_RichText_setVerticalSpace, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("getWrapMode", js_cocos2dx_ui_RichText_getWrapMode, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("formatText", js_cocos2dx_ui_RichText_formatText, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("initWithXML", js_cocos2dx_ui_RichText_initWithXML, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("removeElement", js_cocos2dx_ui_RichText_removeElement, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("ctor", js_cocos2dx_ui_RichText_ctor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END @@ -13545,6 +13761,7 @@ void js_register_cocos2dx_ui_RichText(JSContext *cx, JS::HandleObject global) { static JSFunctionSpec st_funcs[] = { JS_FN("create", js_cocos2dx_ui_RichText_create, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), + JS_FN("createWithXML", js_cocos2dx_ui_RichText_createWithXML, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; diff --git a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.hpp b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.hpp index 155f0722ad..114f59afb4 100644 --- a/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.hpp +++ b/cocos/scripting/js-bindings/auto/jsb_cocos2dx_ui_auto.hpp @@ -423,6 +423,8 @@ bool js_cocos2dx_ui_ScrollView_getScrollBarColor(JSContext *cx, uint32_t argc, j bool js_cocos2dx_ui_ScrollView_jumpToTopLeft(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_ScrollView_jumpToPercentHorizontal(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_ScrollView_jumpToBottomRight(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_ScrollView_setTouchTotalTimeThreshold(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_ScrollView_getTouchTotalTimeThreshold(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_ScrollView_getScrollBarPositionFromCornerForHorizontal(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_ScrollView_setScrollBarWidth(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_ScrollView_setBounceEnabled(JSContext *cx, uint32_t argc, jsval *vp); @@ -698,7 +700,9 @@ bool js_cocos2dx_ui_RichElementImage_constructor(JSContext *cx, uint32_t argc, j void js_cocos2dx_ui_RichElementImage_finalize(JSContext *cx, JSObject *obj); void js_register_cocos2dx_ui_RichElementImage(JSContext *cx, JS::HandleObject global); void register_all_cocos2dx_ui(JSContext* cx, JS::HandleObject obj); +bool js_cocos2dx_ui_RichElementImage_setHeight(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichElementImage_init(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_RichElementImage_setWidth(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichElementImage_create(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichElementImage_RichElementImage(JSContext *cx, uint32_t argc, jsval *vp); @@ -722,10 +726,14 @@ void js_register_cocos2dx_ui_RichText(JSContext *cx, JS::HandleObject global); void register_all_cocos2dx_ui(JSContext* cx, JS::HandleObject obj); bool js_cocos2dx_ui_RichText_insertElement(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_pushBackElement(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_RichText_setWrapMode(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_setVerticalSpace(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_RichText_getWrapMode(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_formatText(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_RichText_initWithXML(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_removeElement(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_create(JSContext *cx, uint32_t argc, jsval *vp); +bool js_cocos2dx_ui_RichText_createWithXML(JSContext *cx, uint32_t argc, jsval *vp); bool js_cocos2dx_ui_RichText_RichText(JSContext *cx, uint32_t argc, jsval *vp); extern JSClass *jsb_cocos2d_ui_HBox_class; diff --git a/cocos/scripting/js-bindings/manual/ScriptingCore.cpp b/cocos/scripting/js-bindings/manual/ScriptingCore.cpp index 62746d2be5..61795a1bbf 100644 --- a/cocos/scripting/js-bindings/manual/ScriptingCore.cpp +++ b/cocos/scripting/js-bindings/manual/ScriptingCore.cpp @@ -1066,21 +1066,24 @@ bool ScriptingCore::executeScript(JSContext *cx, uint32_t argc, jsval *vp) JSString* str = JS::ToString(cx, jsstr); JSStringWrapper path(str); bool res = false; + JS::RootedValue jsret(cx); if (argc == 2 && args.get(1).isString()) { JSString* globalName = args.get(1).toString(); JSStringWrapper name(globalName); JS::RootedObject debugObj(cx, ScriptingCore::getInstance()->getDebugGlobal()); if (debugObj) { - res = ScriptingCore::getInstance()->runScript(path.get(), debugObj); + res = ScriptingCore::getInstance()->requireScript(path.get(), debugObj, cx, &jsret); } else { JS_ReportError(cx, "Invalid global object: %s", name.get()); + args.rval().setUndefined(); return false; } } else { JS::RootedObject glob(cx, JS::CurrentGlobalOrNull(cx)); - res = ScriptingCore::getInstance()->runScript(path.get(), glob); + res = ScriptingCore::getInstance()->requireScript(path.get(), glob, cx, &jsret); } + args.rval().set(jsret); return res; } args.rval().setUndefined(); diff --git a/cocos/scripting/js-bindings/manual/jsb_opengl_functions.cpp b/cocos/scripting/js-bindings/manual/jsb_opengl_functions.cpp index 3d7004b491..70868d2189 100644 --- a/cocos/scripting/js-bindings/manual/jsb_opengl_functions.cpp +++ b/cocos/scripting/js-bindings/manual/jsb_opengl_functions.cpp @@ -13,6 +13,7 @@ #include "js_bindings_core.h" #include "js_manual_conversions.h" #include "jsb_opengl_functions.h" +#include "platform/CCGL.h" // Arguments: GLenum // Ret value: void diff --git a/cocos/scripting/js-bindings/manual/jsb_opengl_manual.cpp b/cocos/scripting/js-bindings/manual/jsb_opengl_manual.cpp index f6036b3264..530ca6a040 100644 --- a/cocos/scripting/js-bindings/manual/jsb_opengl_manual.cpp +++ b/cocos/scripting/js-bindings/manual/jsb_opengl_manual.cpp @@ -28,6 +28,7 @@ #include "js_manual_conversions.h" #include "js_bindings_core.h" #include "jsb_opengl_functions.h" +#include "platform/CCGL.h" // Helper functions that link "glGenXXXs" (OpenGL ES 2.0 spec), with "gl.createXXX" (WebGL spec) diff --git a/cocos/scripting/js-bindings/manual/jsb_opengl_manual.h b/cocos/scripting/js-bindings/manual/jsb_opengl_manual.h index 889a71ebde..59bbb45d6d 100644 --- a/cocos/scripting/js-bindings/manual/jsb_opengl_manual.h +++ b/cocos/scripting/js-bindings/manual/jsb_opengl_manual.h @@ -28,22 +28,9 @@ #include "js_bindings_config.h" #ifdef JSB_INCLUDE_OPENGL -//#include #include "jsapi.h" #include "jsfriendapi.h" -#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED -#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) - -// compatible with iOS -#define glClearDepthf glClearDepth -#define glDepthRangef glDepthRange -#ifndef glReleaseShaderCompiler - #define glReleaseShaderCompiler() -#endif - -#endif // __MAC_OS_X_VERSION_MAX_ALLOWED - // forward declaration of new functions bool JSB_glGetSupportedExtensions(JSContext *cx, uint32_t argc, jsval *vp); diff --git a/cocos/scripting/js-bindings/script/ccui/jsb_ccui_create_apis.js b/cocos/scripting/js-bindings/script/ccui/jsb_ccui_create_apis.js index c14f90fdad..7cb5811da1 100644 --- a/cocos/scripting/js-bindings/script/ccui/jsb_ccui_create_apis.js +++ b/cocos/scripting/js-bindings/script/ccui/jsb_ccui_create_apis.js @@ -125,7 +125,7 @@ ccui.TextField.prototype._ctor = function(placeholder, fontName, fontSize){ }; ccui.RichElementText.prototype._ctor = function(tag, color, opacity, text, fontName, fontSize){ - fontSize !== undefined && this.init(tag, color, opacity, text, fontName, fontSize); + fontSize !== undefined && this.init(tag, color, opacity, text, fontName, fontSize, 0, ""); }; ccui.RichElementImage.prototype._ctor = function(tag, color, opacity, filePath){ diff --git a/cocos/scripting/js-bindings/script/ccui/jsb_ccui_deprecated.js b/cocos/scripting/js-bindings/script/ccui/jsb_ccui_deprecated.js index 5144d54931..67356c4d60 100644 --- a/cocos/scripting/js-bindings/script/ccui/jsb_ccui_deprecated.js +++ b/cocos/scripting/js-bindings/script/ccui/jsb_ccui_deprecated.js @@ -1,3 +1,5 @@ +/*global ccui */ + /* * Copyright (c) 2013-2014 Chukong Technologies Inc. * @@ -25,15 +27,69 @@ var cc = cc || {}; (function() { + var logW = function(oldName, newName) { + cc.log("\n********** \n"+oldName +" was deprecated, please use "+ newName +" instead.\n**********"); + }; - ccui.Text.prototype.setText = function(text){ + ccui.Text.prototype.setText = function(text) { logW("ccui.Text.setText", "ccui.Text.setString"); this.setString(text); }; - ccui.Text.prototype.getStringValue = function(){ + ccui.Text.prototype.getStringValue = function() { logW("ccui.Text.getStringValue", "ccui.Text.getString"); return this.getString(); }; + ccui.PageView.prototype.getCurPageIndex = function() { + logW("ccui.PageView.getCurPageIndex", "ccui.PageView.getCurrentPageIndex"); + return this.getCurrentPageIndex(); + }; + + ccui.PageView.prototype.addWidgetToPage = function(widget, pageIndx) { + logW("ccui.PageView.addWidgetToPage", "ccui.PageView.insertPage"); + return this.insertPage(widget, pageIndx); + }; + + ccui.PageView.prototype.setCurPageIndex = function(index) { + logW("ccui.PageView.setCurPageIndex", "ccui.PageView.setCurrentPageIndex"); + return this.setCurrentPageIndex(index); + }; + + ccui.PageView.prototype.getPages = function() { + logW("ccui.PageView.getPages", "ccui.PageView.getItems"); + return this.getItems(); + }; + + ccui.PageView.prototype.getPage = function(index) { + logW("ccui.PageView.getPage", "ccui.PageView.getItem"); + return this.getItem(index); + }; + + ccui.PageView.prototype.setCustomScrollThreshold = function() { + cc.log("Since v3.9, this method has no effect."); + }; + + ccui.PageView.prototype.getCustomScrollThreshold = function() { + cc.log("Since v3.9, this method has no effect."); + }; + + ccui.PageView.prototype.setUsingCustomScrollThreshold = function() { + cc.log("Since v3.9, this method has no effect."); + }; + + ccui.PageView.prototype.isUsingCustomScrollThreshold = function() { + cc.log("Since v3.9, this method has no effect."); + }; + + ccui.ListView.prototype.requestRefreshView = function() { + logW("ccui.ListView.requestRefreshView", "ccui.ListView.forceDoLayout"); + this.forceDoLayout(); + }; + + ccui.ListView.prototype.refreshView = function() { + logW("ccui.ListView.refreshView", "ccui.ListView.forceDoLayout"); + this.forceDoLayout(); + }; + })(); diff --git a/cocos/scripting/js-bindings/script/debugger/DevToolsUtils.js b/cocos/scripting/js-bindings/script/debugger/DevToolsUtils.js index c3649c569a..4a426cd883 100644 --- a/cocos/scripting/js-bindings/script/debugger/DevToolsUtils.js +++ b/cocos/scripting/js-bindings/script/debugger/DevToolsUtils.js @@ -4,7 +4,20 @@ "use strict"; -function utf16to8(str) { +/* General utilities used throughout devtools. */ + +// var { Ci, Cu, Cc, components } = require("chrome"); +// var Services = require("Services"); +// var promise = require("promise"); + +// loader.lazyRequireGetter(this, "FileUtils", +// "resource://gre/modules/FileUtils.jsm", true); + +function DevToolsUtils() { + +} + +DevToolsUtils.utf16to8 = function utf16to8(str) { var out, i, len, c; out = ""; @@ -31,7 +44,7 @@ function utf16to8(str) { return out; } -function utf8to16(str) { +DevToolsUtils.utf8to16 = function utf8to16(str) { var out, i, len, c; var char2, char3; @@ -63,53 +76,53 @@ function utf8to16(str) { return out; } -var dump = function(msg) { - log(msg); -}; - -/* General utilities used throughout devtools. */ - /** * Turn the error |aError| into a string, without fail. */ -this.safeErrorString = function safeErrorString(aError) { +DevToolsUtils.safeErrorString = function safeErrorString(aError) { try { let errorString = aError.toString(); - if (typeof errorString === "string") { + if (typeof errorString == "string") { // Attempt to attach a stack to |errorString|. If it throws an error, or // isn't a string, don't use it. try { if (aError.stack) { let stack = aError.stack.toString(); - if (typeof stack === "string") { + if (typeof stack == "string") { errorString += "\nStack: " + stack; } } } catch (ee) { } + // Append additional line and column number information to the output, + // since it might not be part of the stringified error. + if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") { + errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber; + } + return errorString; } } catch (ee) { } - return ""; + // We failed to find a good error description, so do the next best thing. + return Object.prototype.toString.call(aError); } - /** * Report that |aWho| threw an exception, |aException|. */ -this.reportException = function reportException(aWho, aException) { - let msg = aWho + " threw an exception: " + safeErrorString(aException); +DevToolsUtils.reportException = function reportException(aWho, aException) { + let msg = aWho + " threw an exception: " + DevToolsUtils.safeErrorString(aException); - dump(msg + "\n"); + log(msg + "\n"); - // if (Components.utils.reportError) { - // /* + // if (Cu && Cu.reportError) { + // * Note that the xpcshell test harness registers an observer for // * console messages, so when we're running tests, this will cause // * the test to quit. - // */ - // Components.utils.reportError(msg); + + // Cu.reportError(msg); // } } @@ -127,7 +140,7 @@ this.reportException = function reportException(aWho, aException) { * (SpiderMonkey does generate good names for anonymous functions, but we * don't have a way to get at them from JavaScript at the moment.) */ -this.makeInfallible = function makeInfallible(aHandler, aName) { +DevToolsUtils.makeInfallible = function makeInfallible(aHandler, aName) { if (!aName) aName = aHandler.name; @@ -139,17 +152,107 @@ this.makeInfallible = function makeInfallible(aHandler, aName) { if (aName) { who += " " + aName; } - reportException(who, ex); + return DevToolsUtils.reportException(who, ex); } } } +/** + * Interleaves two arrays element by element, returning the combined array, like + * a zip. In the case of arrays with different sizes, undefined values will be + * interleaved at the end along with the extra values of the larger array. + * + * @param Array a + * @param Array b + * @returns Array + * The combined array, in the form [a1, b1, a2, b2, ...] + */ +DevToolsUtils.zip = function zip(a, b) { + if (!b) { + return a; + } + if (!a) { + return b; + } + const pairs = []; + for (let i = 0, aLength = a.length, bLength = b.length; + i < aLength || i < bLength; + i++) { + pairs.push([a[i], b[i]]); + } + return pairs; +}; -const executeSoon = aFn => { - Services.tm.mainThread.dispatch({ - run: this.makeInfallible(aFn) - }, Components.interfaces.nsIThread.DISPATCH_NORMAL); + +/** + * Converts an object into an array with 2-element arrays as key/value + * pairs of the object. `{ foo: 1, bar: 2}` would become + * `[[foo, 1], [bar 2]]` (order not guaranteed); + * + * @param object obj + * @returns array + */ +DevToolsUtils.entries = function entries(obj) { + return Object.keys(obj).map(k => [k, obj[k]]); } +/** + * Composes the given functions into a single function, which will + * apply the results of each function right-to-left, starting with + * applying the given arguments to the right-most function. + * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)` + * + * @param ...function funcs + * @returns function + */ +DevToolsUtils.compose = function compose(...funcs) { + return (...args) => { + const initialValue = funcs[funcs.length - 1].apply(null, args); + const leftFuncs = funcs.slice(0, -1); + return leftFuncs.reduceRight((composed, f) => f(composed), + initialValue); + }; +} + + +/** + * Waits for the next tick in the event loop to execute a callback. + */ +DevToolsUtils.executeSoon = function executeSoon(aFn) { + if (isWorker) { + setImmediate(aFn); + } else { + Services.tm.mainThread.dispatch({ + run: DevToolsUtils.makeInfallible(aFn) + }, Ci.nsIThread.DISPATCH_NORMAL); + } +}; + +/** + * Waits for the next tick in the event loop. + * + * @return Promise + * A promise that is resolved after the next tick in the event loop. + */ +DevToolsUtils.waitForTick = function waitForTick() { + let deferred = promise.defer(); + DevToolsUtils.executeSoon(deferred.resolve); + return deferred.promise; +}; + +/** + * Waits for the specified amount of time to pass. + * + * @param number aDelay + * The amount of time to wait, in milliseconds. + * @return Promise + * A promise that is resolved after the specified amount of time passes. + */ +DevToolsUtils.waitForTime = function waitForTime(aDelay) { + let deferred = promise.defer(); + require("Timer").setTimeout(deferred.resolve, aDelay); + return deferred.promise; +}; + /** * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over * very large arrays by yielding to the browser and continuing execution on the @@ -158,16 +261,19 @@ const executeSoon = aFn => { * @param Array aArray * The array being iterated over. * @param Function aFn - * The function called on each item in the array. + * The function called on each item in the array. If a promise is + * returned by this function, iterating over the array will be paused + * until the respective promise is resolved. * @returns Promise * A promise that is resolved once the whole array has been iterated - * over. + * over, and all promises returned by the aFn callback are resolved. */ -this.yieldingEach = function yieldingEach(aArray, aFn) { +DevToolsUtils.yieldingEach = function yieldingEach(aArray, aFn) { const deferred = promise.defer(); let i = 0; let len = aArray.length; + let outstanding = [deferred.promise]; (function loop() { const start = Date.now(); @@ -178,12 +284,12 @@ this.yieldingEach = function yieldingEach(aArray, aFn) { // aren't including time spent in non-JS here, but this is Good // Enough(tm). if (Date.now() - start > 16) { - executeSoon(loop); + DevToolsUtils.executeSoon(loop); return; } try { - aFn(aArray[i++]); + outstanding.push(aFn(aArray[i], i++)); } catch (e) { deferred.reject(e); return; @@ -193,10 +299,9 @@ this.yieldingEach = function yieldingEach(aArray, aFn) { deferred.resolve(); }()); - return deferred.promise; + return promise.all(outstanding); } - /** * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that * allows the lazy getter to be defined on a prototype and work correctly with @@ -210,7 +315,7 @@ this.yieldingEach = function yieldingEach(aArray, aFn) { * The callback that will be called to determine the value. Will be * called with the |this| value of the current instance. */ -this.defineLazyPrototypeGetter = +DevToolsUtils.defineLazyPrototypeGetter = function defineLazyPrototypeGetter(aObject, aKey, aCallback) { Object.defineProperty(aObject, aKey, { configurable: true, @@ -228,3 +333,477 @@ function defineLazyPrototypeGetter(aObject, aKey, aCallback) { }); } +/** + * Safely get the property value from a Debugger.Object for a given key. Walks + * the prototype chain until the property is found. + * + * @param Debugger.Object aObject + * The Debugger.Object to get the value from. + * @param String aKey + * The key to look for. + * @return Any + */ +DevToolsUtils.getProperty = function getProperty(aObj, aKey) { + let root = aObj; + try { + do { + const desc = aObj.getOwnPropertyDescriptor(aKey); + if (desc) { + if ("value" in desc) { + return desc.value; + } + // Call the getter if it's safe. + return DevToolsUtils.hasSafeGetter(desc) ? desc.get.call(root).return : undefined; + } + aObj = aObj.proto; + } while (aObj); + } catch (e) { + // If anything goes wrong report the error and return undefined. + DevToolsUtils.reportException("getProperty", e); + } + return undefined; +}; + +/** + * Determines if a descriptor has a getter which doesn't call into JavaScript. + * + * @param Object aDesc + * The descriptor to check for a safe getter. + * @return Boolean + * Whether a safe getter was found. + */ +DevToolsUtils.hasSafeGetter = function hasSafeGetter(aDesc) { + // Scripted functions that are CCWs will not appear scripted until after + // unwrapping. + // let fn = aDesc.get.unwrap(); + let fn = aDesc.get; + return fn && fn.callable && fn.class == "Function" && fn.script === undefined; +}; + +/** + * Check if it is safe to read properties and execute methods from the given JS + * object. Safety is defined as being protected from unintended code execution + * from content scripts (or cross-compartment code). + * + * See bugs 945920 and 946752 for discussion. + * + * @type Object aObj + * The object to check. + * @return Boolean + * True if it is safe to read properties from aObj, or false otherwise. + */ +DevToolsUtils.isSafeJSObject = function isSafeJSObject(aObj) { + return true; + // If we are running on a worker thread, Cu is not available. In this case, + // we always return false, just to be on the safe side. + // if (isWorker) { + // return false; + // } + + // if (Cu.getGlobalForObject(aObj) == + // Cu.getGlobalForObject(DevToolsUtils.isSafeJSObject)) { + // return true; // aObj is not a cross-compartment wrapper. + // } + + // let principal = Cu.getObjectPrincipal(aObj); + // if (Services.scriptSecurityManager.isSystemPrincipal(principal)) { + // return true; // allow chrome objects + // } + + // return Cu.isXrayWrapper(aObj); +}; + +DevToolsUtils.dumpn = function dumpn(str) { + if (DevToolsUtils.dumpn.wantLogging) { + dump("DBG-SERVER: " + str + "\n"); + } +} + +// We want wantLogging to be writable. The DevToolsUtils object is frozen by the +// loader, so define it on dumpn instead. +DevToolsUtils.dumpn.wantLogging = false; + +/** + * A verbose logger for low-level tracing. + */ +DevToolsUtils.dumpv = function(msg) { + if (DevToolsUtils.dumpv.wantVerbose) { + DevToolsUtils.dumpn(msg); + } +}; + +// We want wantLogging to be writable. The DevToolsUtils object is frozen by the +// loader, so define it on dumpn instead. +DevToolsUtils.dumpv.wantVerbose = false; + +DevToolsUtils.dbg_assert = function dbg_assert(cond, e) { + if (!cond) { + return e; + } +}; + + +/** + * Utility function for updating an object with the properties of + * other objects. + * + * @param aTarget Object + * The object being updated. + * @param aNewAttrs Object + * The rest params are objects to update aTarget with. You + * can pass as many as you like. + */ +DevToolsUtils.update = function update(aTarget, ...aArgs) { + for (let attrs of aArgs) { + for (let key in attrs) { + let desc = Object.getOwnPropertyDescriptor(attrs, key); + + if (desc) { + Object.defineProperty(aTarget, key, desc); + } + } + } + + return aTarget; +} + +/** + * Utility function for getting the values from an object as an array + * + * @param aObject Object + * The object to iterate over + */ +DevToolsUtils.values = function values(aObject) { + return Object.keys(aObject).map(k => aObject[k]); +} + +/** + * Defines a getter on a specified object that will be created upon first use. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter to define on aObject. + * @param aLambda + * A function that returns what the getter should return. This will + * only ever be called once. + */ +DevToolsUtils.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) { + Object.defineProperty(aObject, aName, { + get: function () { + delete aObject[aName]; + return aObject[aName] = aLambda.apply(aObject); + }, + configurable: true, + enumerable: true + }); +}; + +/** + * Defines a getter on a specified object for a module. The module will not + * be imported until first use. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter to define on aObject for the module. + * @param aResource + * The URL used to obtain the module. + * @param aSymbol + * The name of the symbol exported by the module. + * This parameter is optional and defaults to aName. + */ +DevToolsUtils.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName, + aResource, + aSymbol) +{ + this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { + var temp = {}; + Cu.import(aResource, temp); + return temp[aSymbol || aName]; + }); +}; + +DevToolsUtils.defineLazyGetter(this, "NetUtil", () => { + return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil; +}); + +DevToolsUtils.defineLazyGetter(this, "OS", () => { + return Cu.import("resource://gre/modules/osfile.jsm", {}).OS; +}); + +DevToolsUtils.defineLazyGetter(this, "TextDecoder", () => { + return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder; +}); + +DevToolsUtils.defineLazyGetter(this, "NetworkHelper", () => { + return require("devtools/toolkit/webconsole/network-helper"); +}); + +/** + * Performs a request to load the desired URL and returns a promise. + * + * @param aURL String + * The URL we will request. + * @param aOptions Object + * An object with the following optional properties: + * - loadFromCache: if false, will bypass the cache and + * always load fresh from the network (default: true) + * - policy: the nsIContentPolicy type to apply when fetching the URL + * - window: the window to get the loadGroup from + * - charset: the charset to use if the channel doesn't provide one + * @returns Promise that resolves with an object with the following members on + * success: + * - content: the document at that URL, as a string, + * - contentType: the content type of the document + * + * If an error occurs, the promise is rejected with that error. + * + * XXX: It may be better to use nsITraceableChannel to get to the sources + * without relying on caching when we can (not for eval, etc.): + * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/ + */ +function mainThreadFetch(aURL, aOptions={ loadFromCache: true, + policy: Ci.nsIContentPolicy.TYPE_OTHER, + window: null, + charset: null }) { + // Create a channel. + let url = aURL.split(" -> ").pop(); + let channel; + try { + channel = newChannelForURL(url, aOptions); + } catch (ex) { + return promise.reject(ex); + } + + // Set the channel options. + channel.loadFlags = aOptions.loadFromCache + ? channel.LOAD_FROM_CACHE + : channel.LOAD_BYPASS_CACHE; + + if (aOptions.window) { + // Respect private browsing. + channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocumentLoader) + .loadGroup; + } + + let deferred = promise.defer(); + let onResponse = (stream, status, request) => { + if (!components.isSuccessCode(status)) { + deferred.reject(new Error('Failed to fetch ${url}. Code ${status}.')); + return; + } + + try { + // We cannot use NetUtil to do the charset conversion as if charset + // information is not available and our default guess is wrong the method + // might fail and we lose the stream data. This means we can't fall back + // to using the locale default encoding (bug 1181345). + + // Read and decode the data according to the locale default encoding. + let available = stream.available(); + let source = NetUtil.readInputStreamToString(stream, available); + stream.close(); + + // If the channel or the caller has correct charset information, the + // content will be decoded correctly. If we have to fall back to UTF-8 and + // the guess is wrong, the conversion fails and convertToUnicode returns + // the input unmodified. Essentially we try to decode the data as UTF-8 + // and if that fails, we use the locale specific default encoding. This is + // the best we can do if the source does not provide charset info. + let charset = channel.contentCharset || aOptions.charset || "UTF-8"; + let unicodeSource = NetworkHelper.convertToUnicode(source, charset); + + deferred.resolve({ + content: unicodeSource, + contentType: request.contentType + }); + } catch (ex) { + let uri = request.originalURI; + if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) { + // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to + // differentiate between empty files and other errors (bug 1170864). + // This can be removed when bug 982654 is fixed. + + uri.QueryInterface(Ci.nsIFileURL); + let result = OS.File.read(uri.file.path).then(bytes => { + // Convert the bytearray to a String. + let decoder = new TextDecoder(); + let content = decoder.decode(bytes); + + // We can't detect the contentType without opening a channel + // and that failed already. This is the best we can do here. + return { + content, + contentType: "text/plain" + }; + }); + + deferred.resolve(result); + } else { + deferred.reject(ex); + } + } + }; + + // Open the channel + try { + NetUtil.asyncFetch(channel, onResponse); + } catch (ex) { + return promise.reject(ex); + } + + return deferred.promise; +} + +/** + * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel. + * + * @param {String} url - The URL to open a channel for. + * @param {Object} options - The options object passed to @method fetch. + * @return {nsIChannel} - The newly created channel. Throws on failure. + */ +function newChannelForURL(url, { policy }) { + let channelOptions = { + contentPolicyType: policy, + loadUsingSystemPrincipal: true, + uri: url + }; + + try { + return NetUtil.newChannel(channelOptions); + } catch (e) { + // In the xpcshell tests, the script url is the absolute path of the test + // file, which will make a malformed URI error be thrown. Add the file + // scheme to see if it helps. + channelOptions.uri = "file://" + url; + + return NetUtil.newChannel(channelOptions); + } +} + +// Fetch is defined differently depending on whether we are on the main thread +// or a worker thread. +if (!this.isWorker) { + DevToolsUtils.fetch = mainThreadFetch; +} else { + // Services is not available in worker threads, nor is there any other way + // to fetch a URL. We need to enlist the help from the main thread here, by + // issuing an rpc request, to fetch the URL on our behalf. + DevToolsUtils.fetch = function (url, options) { + return rpc("fetch", url, options); + } +} + +/** + * Returns a promise that is resolved or rejected when all promises have settled + * (resolved or rejected). + * + * This differs from Promise.all, which will reject immediately after the first + * rejection, instead of waiting for the remaining promises to settle. + * + * @param values + * Iterable of promises that may be pending, resolved, or rejected. When + * when all promises have settled (resolved or rejected), the returned + * promise will be resolved or rejected as well. + * + * @return A new promise that is fulfilled when all values have settled + * (resolved or rejected). Its resolution value will be an array of all + * resolved values in the given order, or undefined if values is an + * empty array. The reject reason will be forwarded from the first + * promise in the list of given promises to be rejected. + */ +DevToolsUtils.settleAll = values => { + if (values === null || typeof(values[Symbol.iterator]) != "function") { + throw new Error("settleAll() expects an iterable."); + } + + let deferred = promise.defer(); + + values = Array.isArray(values) ? values : [...values]; + let countdown = values.length; + let resolutionValues = new Array(countdown); + let rejectionValue; + let rejectionOccurred = false; + + if (!countdown) { + deferred.resolve(resolutionValues); + return deferred.promise; + } + + function checkForCompletion() { + if (--countdown > 0) { + return; + } + if (!rejectionOccurred) { + deferred.resolve(resolutionValues); + } else { + deferred.reject(rejectionValue); + } + } + + for (let i = 0; i < values.length; i++) { + let index = i; + let value = values[i]; + let resolver = result => { + resolutionValues[index] = result; + checkForCompletion(); + }; + let rejecter = error => { + if (!rejectionOccurred) { + rejectionValue = error; + rejectionOccurred = true; + } + checkForCompletion(); + }; + + if (value && typeof(value.then) == "function") { + value.then(resolver, rejecter); + } else { + // Given value is not a promise, forward it as a resolution value. + resolver(value); + } + } + + return deferred.promise; +}; + +/** + * When the testing flag is set, various behaviors may be altered from + * production mode, typically to enable easier testing or enhanced debugging. + */ +var testing = false; +Object.defineProperty(DevToolsUtils, "testing", { + get: function() { + return testing; + }, + set: function(state) { + testing = state; + } +}); + +/** + * Open the file at the given path for reading. + * + * @param {String} filePath + * + * @returns Promise + */ +DevToolsUtils.openFileStream = function (filePath) { + return new Promise((resolve, reject) => { + const uri = NetUtil.newURI(new FileUtils.File(filePath)); + NetUtil.asyncFetch( + { uri, loadUsingSystemPrincipal: true }, + (stream, result) => { + if (!components.isSuccessCode(result)) { + reject(new Error('Could not open "${filePath}": result = ${result}')); + return; + } + + resolve(stream); + } + ); + }); +} diff --git a/cocos/scripting/js-bindings/script/debugger/actors/common.js b/cocos/scripting/js-bindings/script/debugger/actors/common.js new file mode 100644 index 0000000000..14f4f97cc2 --- /dev/null +++ b/cocos/scripting/js-bindings/script/debugger/actors/common.js @@ -0,0 +1,277 @@ +/** + * An OriginalLocation represents a location in an original source. + * + * @param SourceActor actor + * A SourceActor representing an original source. + * @param Number line + * A line within the given source. + * @param Number column + * A column within the given line. + * @param String name + * The name of the symbol corresponding to this OriginalLocation. + */ + +function OriginalLocation(actor, line, column, name) { + this._connection = actor ? actor.conn : null; + this._actorID = actor ? actor.actorID : undefined; + this._line = line; + this._column = column; + this._name = name; +} + +OriginalLocation.fromGeneratedLocation = function (generatedLocation) { + return new OriginalLocation( + generatedLocation.generatedSourceActor, + generatedLocation.generatedLine, + generatedLocation.generatedColumn + ); +}; + +OriginalLocation.prototype = { + get originalSourceActor() { + return this._connection ? this._connection.getActor(this._actorID) : null; + }, + + get originalUrl() { + let actor = this.originalSourceActor; + let source = actor.source; + return source ? source.url : actor._originalUrl; + }, + + get originalLine() { + return this._line; + }, + + get originalColumn() { + return this._column; + }, + + get originalName() { + return this._name; + }, + + get generatedSourceActor() { + throw new Error("Shouldn't access generatedSourceActor from an OriginalLocation"); + }, + + get generatedLine() { + throw new Error("Shouldn't access generatedLine from an OriginalLocation"); + }, + + get generatedColumn() { + throw new Error("Shouldn't access generatedColumn from an Originallocation"); + }, + + equals: function (other) { + return this.originalSourceActor.url == other.originalSourceActor.url && + this.originalLine === other.originalLine && + (this.originalColumn === undefined || + other.originalColumn === undefined || + this.originalColumn === other.originalColumn); + }, + + toJSON: function () { + return { + source: this.originalSourceActor.form(), + line: this.originalLine, + column: this.originalColumn + }; + } +}; + +/** + * A GeneratedLocation represents a location in a generated source. + * + * @param SourceActor actor + * A SourceActor representing a generated source. + * @param Number line + * A line within the given source. + * @param Number column + * A column within the given line. + */ +function GeneratedLocation(actor, line, column, lastColumn) { + this._connection = actor ? actor.conn : null; + this._actorID = actor ? actor.actorID : undefined; + this._line = line; + this._column = column; + this._lastColumn = (lastColumn !== undefined) ? lastColumn : column + 1; +} + +GeneratedLocation.fromOriginalLocation = function (originalLocation) { + return new GeneratedLocation( + originalLocation.originalSourceActor, + originalLocation.originalLine, + originalLocation.originalColumn + ); +}; + +GeneratedLocation.prototype = { + get originalSourceActor() { + throw new Error(); + }, + + get originalUrl() { + throw new Error("Shouldn't access originalUrl from a GeneratedLocation"); + }, + + get originalLine() { + throw new Error("Shouldn't access originalLine from a GeneratedLocation"); + }, + + get originalColumn() { + throw new Error("Shouldn't access originalColumn from a GeneratedLocation"); + }, + + get originalName() { + throw new Error("Shouldn't access originalName from a GeneratedLocation"); + }, + + get generatedSourceActor() { + return this._connection ? this._connection.getActor(this._actorID) : null; + }, + + get generatedLine() { + return this._line; + }, + + get generatedColumn() { + return this._column; + }, + + get generatedLastColumn() { + return this._lastColumn; + }, + + equals: function (other) { + return this.generatedSourceActor.url == other.generatedSourceActor.url && + this.generatedLine === other.generatedLine && + (this.generatedColumn === undefined || + other.generatedColumn === undefined || + this.generatedColumn === other.generatedColumn); + }, + + toJSON: function () { + return { + source: this.generatedSourceActor.form(), + line: this.generatedLine, + column: this.generatedColumn, + lastColumn: this.generatedLastColumn + }; + } +}; + +getOffsetColumn = function getOffsetColumn(aOffset, aScript) { + let bestOffsetMapping = null; + for (let offsetMapping of aScript.getAllColumnOffsets()) { + if (!bestOffsetMapping || + (offsetMapping.offset <= aOffset && + offsetMapping.offset > bestOffsetMapping.offset)) { + bestOffsetMapping = offsetMapping; + } + } + + if (!bestOffsetMapping) { + // XXX: Try not to completely break the experience of using the debugger for + // the user by assuming column 0. Simultaneously, report the error so that + // there is a paper trail if the assumption is bad and the debugging + // experience becomes wonky. + reportError(new Error("Could not find a column for offset " + aOffset + + " in the script " + aScript)); + return 0; + } + + return bestOffsetMapping.columnNumber; +} + +/** + * Construct an ActorPool. + * + * ActorPools are actorID -> actor mapping and storage. These are + * used to accumulate and quickly dispose of groups of actors that + * share a lifetime. + */ +function ActorPool(aConnection) +{ + this.conn = aConnection; + this._cleanups = {}; + this._actors = {}; +} + +ActorPool.prototype = { + /** + * Add an actor to the actor pool. If the actor doesn't have an ID, + * allocate one from the connection. + * + * @param aActor object + * The actor implementation. If the object has a + * 'disconnect' property, it will be called when the actor + * pool is cleaned up. + */ + addActor: function AP_addActor(aActor) { + aActor.conn = this.conn; + if (!aActor.actorID) { + let prefix = aActor.actorPrefix; + if (!prefix && typeof aActor == "function") { + // typeName is a convention used with protocol.js-based actors + prefix = aActor.prototype.actorPrefix || aActor.prototype.typeName; + } + aActor.actorID = this.conn.allocID(prefix || undefined); + } + + if (aActor.registeredPool) { + aActor.registeredPool.removeActor(aActor); + } + aActor.registeredPool = this; + + this._actors[aActor.actorID] = aActor; + if (aActor.disconnect) { + this._cleanups[aActor.actorID] = aActor; + } + }, + + get: function AP_get(aActorID) { + return this._actors[aActorID] || undefined; + }, + + has: function AP_has(aActorID) { + return aActorID in this._actors; + }, + + /** + * Returns true if the pool is empty. + */ + isEmpty: function AP_isEmpty() { + return Object.keys(this._actors).length == 0; + }, + + /** + * Remove an actor from the actor pool. + */ + removeActor: function AP_remove(aActor) { + delete this._actors[aActor.actorID]; + delete this._cleanups[aActor.actorID]; + }, + + /** + * Match the api expected by the protocol library. + */ + unmanage: function(aActor) { + return this.removeActor(aActor); + }, + + /** + * Run all actor cleanups. + */ + cleanup: function AP_cleanup() { + for (let id in this._cleanups) { + this._cleanups[id].disconnect(); + } + this._cleanups = {}; + }, + + forEach: function(callback) { + for (let name in this._actors) { + callback(this._actors[name]); + } + }, +} \ No newline at end of file diff --git a/cocos/scripting/js-bindings/script/debugger/actors/object.js b/cocos/scripting/js-bindings/script/debugger/actors/object.js new file mode 100644 index 0000000000..71049a1d5e --- /dev/null +++ b/cocos/scripting/js-bindings/script/debugger/actors/object.js @@ -0,0 +1,1984 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=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/. */ + +"use strict"; + +// const { Cu, Ci } = require("chrome"); +// const { GeneratedLocation } = require("devtools/server/actors/common"); +// const { DebuggerServer } = require("devtools/server/main") +// const DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); +// const { dbg_assert, dumpn } = DevToolsUtils; +// const PromiseDebugging = require("PromiseDebugging"); + +const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", + "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array", + "Float64Array"]; + +// Number of items to preview in objects, arrays, maps, sets, lists, +// collections, etc. +const OBJECT_PREVIEW_MAX_ITEMS = 10; + +/** + * Creates an actor for the specified object. + * + * @param obj Debugger.Object + * The debuggee object. + * @param hooks Object + * A collection of abstract methods that are implemented by the caller. + * ObjectActor requires the following functions to be implemented by + * the caller: + * - createValueGrip + * Creates a value grip for the given object + * - sources + * TabSources getter that manages the sources of a thread + * - createEnvironmentActor + * Creates and return an environment actor + * - getGripDepth + * An actor's grip depth getter + * - incrementGripDepth + * Increment the actor's grip depth + * - decrementGripDepth + * Decrement the actor's grip depth + * - globalDebugObject + * The Debuggee Global Object as given by the ThreadActor + */ +function ObjectActor(obj, { + createValueGrip, + sources, + createEnvironmentActor, + getGripDepth, + incrementGripDepth, + decrementGripDepth, + getGlobalDebugObject +}) { + dbg_assert(!obj.optimizedOut, + "Should not create object actors for optimized out values!"); + this.obj = obj; + this.hooks = { + createValueGrip, + sources, + createEnvironmentActor, + getGripDepth, + incrementGripDepth, + decrementGripDepth, + getGlobalDebugObject + }; + this.iterators = new Set(); +} + +ObjectActor.prototype = { + actorPrefix: "obj", + + /** + * Returns a grip for this actor for returning in a protocol message. + */ + grip: function() { + this.hooks.incrementGripDepth(); + + let g = { + "type": "object", + "class": this.obj.class, + "actor": this.actorID, + "extensible": this.obj.isExtensible(), + "frozen": this.obj.isFrozen(), + "sealed": this.obj.isSealed() + }; + + if (this.obj.class != "DeadObject") { + if (this.obj.class == "Promise") { + g.promiseState = this._createPromiseState(); + } + + // FF40+: Allow to know how many properties an object has + // to lazily display them when there is a bunch. + // Throws on some MouseEvent object in tests. + try { + // Bug 1163520: Assert on internal functions + if (this.obj.class != "Function") { + g.ownPropertyLength = this.obj.getOwnPropertyNames().length; + } + } catch(e) {} + + let raw = this.obj.unsafeDereference(); + + // If Cu is not defined, we are running on a worker thread, where xrays + // don't exist. + // if (Cu) { + // raw = Cu.unwaiveXrays(raw); + // } + + if (!DevToolsUtils.isSafeJSObject(raw)) { + raw = null; + } + + // let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] || + // DebuggerServer.ObjectActorPreviewers.Object; + // for (let fn of previewers) { + // try { + // if (fn(this, g, raw)) { + // break; + // } + // } catch (e) { + // let msg = "ObjectActor.prototype.grip previewer function"; + // DevToolsUtils.reportException(msg, e); + // } + // } + } + + this.hooks.decrementGripDepth(); + return g; + }, + + /** + * Returns an object exposing the internal Promise state. + */ + _createPromiseState: function() { + const { state, value, reason } = getPromiseState(this.obj); + let promiseState = { state }; + let rawPromise = this.obj.unsafeDereference(); + + if (state == "fulfilled") { + promiseState.value = this.hooks.createValueGrip(value); + } else if (state == "rejected") { + promiseState.reason = this.hooks.createValueGrip(reason); + } + + promiseState.creationTimestamp = Date.now() - + PromiseDebugging.getPromiseLifetime(rawPromise); + + // If the promise is not settled, avoid adding the timeToSettle property + // and catch the error thrown by PromiseDebugging.getTimeToSettle. + try { + promiseState.timeToSettle = PromiseDebugging.getTimeToSettle(rawPromise); + } catch(e) {} + + return promiseState; + }, + + /** + * Releases this actor from the pool. + */ + release: function() { + if (this.registeredPool.objectActors) { + this.registeredPool.objectActors.delete(this.obj); + } + this.iterators.forEach(actor => this.registeredPool.removeActor(actor)); + this.iterators.clear(); + this.registeredPool.removeActor(this); + }, + + /** + * Handle a protocol request to provide the definition site of this function + * object. + */ + onDefinitionSite: function() { + if (this.obj.class != "Function") { + return { + from: this.actorID, + error: "objectNotFunction", + message: this.actorID + " is not a function." + }; + } + + if (!this.obj.script) { + return { + from: this.actorID, + error: "noScript", + message: this.actorID + " has no Debugger.Script" + }; + } + + return this.hooks.sources().getOriginalLocation(new GeneratedLocation( + this.hooks.sources().createNonSourceMappedActor(this.obj.script.source), + this.obj.script.startLine, + 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn + )).then((originalLocation) => { + return { + source: originalLocation.originalSourceActor.form(), + line: originalLocation.originalLine, + column: originalLocation.originalColumn + }; + }); + }, + + /** + * Handle a protocol request to provide the names of the properties defined on + * the object and not its prototype. + */ + onOwnPropertyNames: function() { + return { from: this.actorID, + ownPropertyNames: this.obj.getOwnPropertyNames() }; + }, + + /** + * Creates an actor to iterate over an object property names and values. + * See PropertyIteratorActor constructor for more info about options param. + * + * @param request object + * The protocol request object. + */ + onEnumProperties: function(request) { + let actor = new PropertyIteratorActor(this, request.options); + this.registeredPool.addActor(actor); + this.iterators.add(actor); + return { iterator: actor.grip() }; + }, + + /** + * Handle a protocol request to provide the prototype and own properties of + * the object. + */ + onPrototypeAndProperties: function() { + let ownProperties = Object.create(null); + let names; + try { + names = this.obj.getOwnPropertyNames(); + } catch (ex) { + log('exception: ' + ex); + log(ex.stack); + // The above can throw if this.obj points to a dead object. + // TODO: we should use Cu.isDeadWrapper() - see bug 885800. + return { from: this.actorID, + prototype: this.hooks.createValueGrip(null), + ownProperties: ownProperties, + safeGetterValues: Object.create(null) }; + } + try { + for (let name of names) { + ownProperties[name] = this._propertyDescriptor(name); + } + return { from: this.actorID, + prototype: this.hooks.createValueGrip(this.obj.proto), + ownProperties: ownProperties, + safeGetterValues: this._findSafeGetterValues(names) }; + } catch(ex) { + log('exception: ' + ex); + log(ex.stack); + } + }, + + /** + * Find the safe getter values for the current Debugger.Object, |this.obj|. + * + * @private + * @param array ownProperties + * The array that holds the list of known ownProperties names for + * |this.obj|. + * @param number [limit=0] + * Optional limit of getter values to find. + * @return object + * An object that maps property names to safe getter descriptors as + * defined by the remote debugging protocol. + */ + _findSafeGetterValues: function(ownProperties, limit = 0) { + let safeGetterValues = Object.create(null); + let obj = this.obj; + let level = 0, i = 0; + + while (obj) { + let getters = this._findSafeGetters(obj); + for (let name of getters) { + // Avoid overwriting properties from prototypes closer to this.obj. Also + // avoid providing safeGetterValues from prototypes if property |name| + // is already defined as an own property. + if (name in safeGetterValues || + (obj != this.obj && ownProperties.indexOf(name) !== -1)) { + continue; + } + + // Ignore __proto__ on Object.prototye. + if (!obj.proto && name == "__proto__") { + continue; + } + + let desc = null, getter = null; + try { + desc = obj.getOwnPropertyDescriptor(name); + getter = desc.get; + } catch (ex) { + // The above can throw if the cache becomes stale. + } + if (!getter) { + obj._safeGetters = null; + continue; + } + + let result = getter.call(this.obj); + if (result && !("throw" in result)) { + let getterValue = undefined; + if ("return" in result) { + getterValue = result.return; + } else if ("yield" in result) { + getterValue = result.yield; + } + // WebIDL attributes specified with the LenientThis extended attribute + // return undefined and should be ignored. + if (getterValue !== undefined) { + safeGetterValues[name] = { + getterValue: this.hooks.createValueGrip(getterValue), + getterPrototypeLevel: level, + enumerable: desc.enumerable, + writable: level == 0 ? desc.writable : true, + }; + if (limit && ++i == limit) { + break; + } + } + } + } + if (limit && i == limit) { + break; + } + + obj = obj.proto; + level++; + } + + return safeGetterValues; + }, + + /** + * Find the safe getters for a given Debugger.Object. Safe getters are native + * getters which are safe to execute. + * + * @private + * @param Debugger.Object object + * The Debugger.Object where you want to find safe getters. + * @return Set + * A Set of names of safe getters. This result is cached for each + * Debugger.Object. + */ + _findSafeGetters: function(object) { + if (object._safeGetters) { + return object._safeGetters; + } + + let getters = new Set(); + let names = []; + try { + names = object.getOwnPropertyNames() + } catch (ex) { + // Calling getOwnPropertyNames() on some wrapped native prototypes is not + // allowed: "cannot modify properties of a WrappedNative". See bug 952093. + } + + for (let name of names) { + let desc = null; + try { + desc = object.getOwnPropertyDescriptor(name); + } catch (e) { + // Calling getOwnPropertyDescriptor on wrapped native prototypes is not + // allowed (bug 560072). + } + if (!desc || desc.value !== undefined || !("get" in desc)) { + continue; + } + + if (DevToolsUtils.hasSafeGetter(desc)) { + getters.add(name); + } + } + + object._safeGetters = getters; + return getters; + }, + + /** + * Handle a protocol request to provide the prototype of the object. + */ + onPrototype: function() { + return { from: this.actorID, + prototype: this.hooks.createValueGrip(this.obj.proto) }; + }, + + /** + * Handle a protocol request to provide the property descriptor of the + * object's specified property. + * + * @param request object + * The protocol request object. + */ + onProperty: function(request) { + if (!request.name) { + return { error: "missingParameter", + message: "no property name was specified" }; + } + + return { from: this.actorID, + descriptor: this._propertyDescriptor(request.name) }; + }, + + /** + * Handle a protocol request to provide the display string for the object. + */ + onDisplayString: function() { + const string = stringify(this.obj); + return { from: this.actorID, + displayString: this.hooks.createValueGrip(string) }; + }, + + /** + * A helper method that creates a property descriptor for the provided object, + * properly formatted for sending in a protocol response. + * + * @private + * @param string name + * The property that the descriptor is generated for. + * @param boolean [onlyEnumerable] + * Optional: true if you want a descriptor only for an enumerable + * property, false otherwise. + * @return object|undefined + * The property descriptor, or undefined if this is not an enumerable + * property and onlyEnumerable=true. + */ + _propertyDescriptor: function(name, onlyEnumerable) { + let desc; + try { + desc = this.obj.getOwnPropertyDescriptor(name); + } catch (e) { + // Calling getOwnPropertyDescriptor on wrapped native prototypes is not + // allowed (bug 560072). Inform the user with a bogus, but hopefully + // explanatory, descriptor. + return { + configurable: false, + writable: false, + enumerable: false, + value: e.name + }; + } + + if (!desc || onlyEnumerable && !desc.enumerable) { + return undefined; + } + + let retval = { + configurable: desc.configurable, + enumerable: desc.enumerable + }; + + if ("value" in desc) { + retval.writable = desc.writable; + retval.value = this.hooks.createValueGrip(desc.value); + } else { + if ("get" in desc) { + retval.get = this.hooks.createValueGrip(desc.get); + } + if ("set" in desc) { + retval.set = this.hooks.createValueGrip(desc.set); + } + } + return retval; + }, + + /** + * Handle a protocol request to provide the source code of a function. + * + * @param request object + * The protocol request object. + */ + onDecompile: function(request) { + if (this.obj.class !== "Function") { + return { error: "objectNotFunction", + message: "decompile request is only valid for object grips " + + "with a 'Function' class." }; + } + + return { from: this.actorID, + decompiledCode: this.obj.decompile(!!request.pretty) }; + }, + + /** + * Handle a protocol request to provide the parameters of a function. + */ + onParameterNames: function() { + if (this.obj.class !== "Function") { + return { error: "objectNotFunction", + message: "'parameterNames' request is only valid for object " + + "grips with a 'Function' class." }; + } + + return { parameterNames: this.obj.parameterNames }; + }, + + /** + * Handle a protocol request to release a thread-lifetime grip. + */ + onRelease: function() { + this.release(); + return {}; + }, + + /** + * Handle a protocol request to provide the lexical scope of a function. + */ + onScope: function() { + if (this.obj.class !== "Function") { + return { error: "objectNotFunction", + message: "scope request is only valid for object grips with a" + + " 'Function' class." }; + } + + let envActor = this.hooks.createEnvironmentActor(this.obj.environment, + this.registeredPool); + if (!envActor) { + return { error: "notDebuggee", + message: "cannot access the environment of this function." }; + } + + return { from: this.actorID, scope: envActor.form() }; + }, + + /** + * Handle a protocol request to get the list of dependent promises of a + * promise. + * + * @return object + * Returns an object containing an array of object grips of the + * dependent promises + */ + onDependentPromises: function() { + if (this.obj.class != "Promise") { + return { error: "objectNotPromise", + message: "'dependentPromises' request is only valid for " + + "object grips with a 'Promise' class." }; + } + + let rawPromise = this.obj.unsafeDereference(); + let promises = PromiseDebugging.getDependentPromises(rawPromise).map(p => + this.hooks.createValueGrip(this.obj.makeDebuggeeValue(p))); + + return { promises }; + }, + + /** + * Handle a protocol request to get the allocation stack of a promise. + */ + onAllocationStack: function() { + if (this.obj.class != "Promise") { + return { error: "objectNotPromise", + message: "'allocationStack' request is only valid for " + + "object grips with a 'Promise' class." }; + } + + let rawPromise = this.obj.unsafeDereference(); + let stack = PromiseDebugging.getAllocationStack(rawPromise); + let allocationStacks = []; + + while (stack) { + if (stack.source) { + let source = this._getSourceOriginalLocation(stack); + + if (source) { + allocationStacks.push(source); + } + } + stack = stack.parent; + } + + return Promise.all(allocationStacks).then(stacks => { + return { allocationStack: stacks }; + }); + }, + + /** + * Handle a protocol request to get the fulfillment stack of a promise. + */ + onFulfillmentStack: function() { + if (this.obj.class != "Promise") { + return { error: "objectNotPromise", + message: "'fulfillmentStack' request is only valid for " + + "object grips with a 'Promise' class." }; + } + + let rawPromise = this.obj.unsafeDereference(); + let stack = PromiseDebugging.getFullfillmentStack(rawPromise); + let fulfillmentStacks = []; + + while (stack) { + if (stack.source) { + let source = this._getSourceOriginalLocation(stack); + + if (source) { + fulfillmentStacks.push(source); + } + } + stack = stack.parent; + } + + return Promise.all(fulfillmentStacks).then(stacks => { + return { fulfillmentStack: stacks }; + }); + }, + + /** + * Handle a protocol request to get the rejection stack of a promise. + */ + onRejectionStack: function() { + if (this.obj.class != "Promise") { + return { error: "objectNotPromise", + message: "'rejectionStack' request is only valid for " + + "object grips with a 'Promise' class." }; + } + + let rawPromise = this.obj.unsafeDereference(); + let stack = PromiseDebugging.getRejectionStack(rawPromise); + let rejectionStacks = []; + + while (stack) { + if (stack.source) { + let source = this._getSourceOriginalLocation(stack); + + if (source) { + rejectionStacks.push(source); + } + } + stack = stack.parent; + } + + return Promise.all(rejectionStacks).then(stacks => { + return { rejectionStack: stacks }; + }); + }, + + /** + * Helper function for fetching the source location of a SavedFrame stack. + * + * @param SavedFrame stack + * The promise allocation stack frame + * @return object + * Returns an object containing the source location of the SavedFrame + * stack. + */ + _getSourceOriginalLocation: function(stack) { + let source; + + // Catch any errors if the source actor cannot be found + try { + source = this.hooks.sources().getSourceActorByURL(stack.source); + } catch(e) {} + + if (!source) { + return null; + } + + return this.hooks.sources().getOriginalLocation(new GeneratedLocation( + source, + stack.line, + stack.column + )).then((originalLocation) => { + return { + source: originalLocation.originalSourceActor.form(), + line: originalLocation.originalLine, + column: originalLocation.originalColumn, + functionDisplayName: stack.functionDisplayName + }; + }); + } +}; + +ObjectActor.prototype.requestTypes = { + "definitionSite": ObjectActor.prototype.onDefinitionSite, + "parameterNames": ObjectActor.prototype.onParameterNames, + "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties, + "enumProperties": ObjectActor.prototype.onEnumProperties, + "prototype": ObjectActor.prototype.onPrototype, + "property": ObjectActor.prototype.onProperty, + "displayString": ObjectActor.prototype.onDisplayString, + "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames, + "decompile": ObjectActor.prototype.onDecompile, + "release": ObjectActor.prototype.onRelease, + "scope": ObjectActor.prototype.onScope, + "dependentPromises": ObjectActor.prototype.onDependentPromises, + "allocationStack": ObjectActor.prototype.onAllocationStack, + "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack, + "rejectionStack": ObjectActor.prototype.onRejectionStack +}; + +/** + * Creates an actor to iterate over an object's property names and values. + * + * @param objectActor ObjectActor + * The object actor. + * @param options Object + * A dictionary object with various boolean attributes: + * - ignoreSafeGetters Boolean + * If true, do not iterate over safe getters. + * - ignoreIndexedProperties Boolean + * If true, filters out Array items. + * e.g. properties names between `0` and `object.length`. + * - ignoreNonIndexedProperties Boolean + * If true, filters out items that aren't array items + * e.g. properties names that are not a number between `0` + * and `object.length`. + * - sort Boolean + * If true, the iterator will sort the properties by name + * before dispatching them. + * - query String + * If non-empty, will filter the properties by names and values + * containing this query string. The match is not case-sensitive. + * Regarding value filtering it just compare to the stringification + * of the property value. + */ +function PropertyIteratorActor(objectActor, options){ + this.objectActor = objectActor; + + let ownProperties = Object.create(null); + let names = []; + try { + names = this.objectActor.obj.getOwnPropertyNames(); + } catch (ex) {} + + let safeGetterValues = {}; + let safeGetterNames = []; + if (!options.ignoreSafeGetters) { + // Merge the safe getter values into the existing properties list. + safeGetterValues = this.objectActor._findSafeGetterValues(names); + safeGetterNames = Object.keys(safeGetterValues); + for (let name of safeGetterNames) { + if (names.indexOf(name) === -1) { + names.push(name); + } + } + } + + if (options.ignoreIndexedProperties || options.ignoreNonIndexedProperties) { + let length = DevToolsUtils.getProperty(this.objectActor.obj, "length"); + if (typeof(length) !== "number") { + // Pseudo arrays are flagged as ArrayLike if they have + // subsequent indexed properties without having any length attribute. + length = 0; + for (let key of names) { + if (isNaN(key) || key != length++) { + break; + } + } + } + + if (options.ignoreIndexedProperties) { + names = names.filter(i => { + // Use parseFloat in order to reject floats... + // (parseInt converts floats to integer) + // (Number(str) converts spaces to 0) + i = parseFloat(i); + return !Number.isInteger(i) || i < 0 || i >= length; + }); + } + + if (options.ignoreNonIndexedProperties) { + names = names.filter(i => { + i = parseFloat(i); + return Number.isInteger(i) && i >= 0 && i < length; + }); + } + } + + if (options.query) { + let { query } = options; + query = query.toLowerCase(); + names = names.filter(name => { + // Filter on attribute names + if (name.toLowerCase().includes(query)) { + return true; + } + // and then on attribute values + let desc; + try { + desc = this.obj.getOwnPropertyDescriptor(name); + } catch(e) {} + if (desc && desc.value && + String(desc.value).includes(query)) { + return true; + } + return false; + }); + } + + if (options.sort) { + names.sort(); + } + + // Now build the descriptor list + for (let name of names) { + let desc = this.objectActor._propertyDescriptor(name); + if (!desc) { + desc = safeGetterValues[name]; + } + else if (name in safeGetterValues) { + // Merge the safe getter values into the existing properties list. + let { getterValue, getterPrototypeLevel } = safeGetterValues[name]; + desc.getterValue = getterValue; + desc.getterPrototypeLevel = getterPrototypeLevel; + } + ownProperties[name] = desc; + } + + this.names = names; + this.ownProperties = ownProperties; +} + +PropertyIteratorActor.prototype = { + actorPrefix: "propertyIterator", + + grip: function() { + return { + type: "propertyIterator", + actor: this.actorID, + count: this.names.length + }; + }, + + names: function({ indexes }) { + let list = []; + for (let idx of indexes) { + list.push(this.names[idx]); + } + return { + names: list + }; + }, + + slice: function({ start, count }) { + let names = this.names.slice(start, start + count); + let props = Object.create(null); + for (let name of names) { + props[name] = this.ownProperties[name]; + } + return { + ownProperties: props + }; + }, + + all: function() { + return { + ownProperties: this.ownProperties + }; + } +}; + +PropertyIteratorActor.prototype.requestTypes = { + "names": PropertyIteratorActor.prototype.names, + "slice": PropertyIteratorActor.prototype.slice, + "all": PropertyIteratorActor.prototype.all, +}; + +/** + * Functions for adding information to ObjectActor grips for the purpose of + * having customized output. This object holds arrays mapped by + * Debugger.Object.prototype.class. + * + * In each array you can add functions that take two + * arguments: + * - the ObjectActor instance and its hooks to make a preview for, + * - the grip object being prepared for the client, + * - the raw JS object after calling Debugger.Object.unsafeDereference(). This + * argument is only provided if the object is safe for reading properties and + * executing methods. See DevToolsUtils.isSafeJSObject(). + * + * Functions must return false if they cannot provide preview + * information for the debugger object, or true otherwise. + */ +DebuggerServer.ObjectActorPreviewers = { + String: [function({obj, hooks}, grip) { + let result = genericObjectPreviewer("String", String, obj, hooks); + let length = DevToolsUtils.getProperty(obj, "length"); + + if (!result || typeof length != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: length + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let items = grip.preview.items = []; + + const max = Math.min(result.value.length, OBJECT_PREVIEW_MAX_ITEMS); + for (let i = 0; i < max; i++) { + let value = hooks.createValueGrip(result.value[i]); + items.push(value); + } + + return true; + }], + + Boolean: [function({obj, hooks}, grip) { + let result = genericObjectPreviewer("Boolean", Boolean, obj, hooks); + if (result) { + grip.preview = result; + return true; + } + + return false; + }], + + Number: [function({obj, hooks}, grip) { + let result = genericObjectPreviewer("Number", Number, obj, hooks); + if (result) { + grip.preview = result; + return true; + } + + return false; + }], + + Function: [function({obj, hooks}, grip) { + if (obj.name) { + grip.name = obj.name; + } + + if (obj.displayName) { + grip.displayName = obj.displayName.substr(0, 500); + } + + if (obj.parameterNames) { + grip.parameterNames = obj.parameterNames; + } + + // Check if the developer has added a de-facto standard displayName + // property for us to use. + let userDisplayName; + try { + userDisplayName = obj.getOwnPropertyDescriptor("displayName"); + } catch (e) { + // Calling getOwnPropertyDescriptor with displayName might throw + // with "permission denied" errors for some functions. + dumpn(e); + } + + if (userDisplayName && typeof userDisplayName.value == "string" && + userDisplayName.value) { + grip.userDisplayName = hooks.createValueGrip(userDisplayName.value); + } + + let dbgGlobal = hooks.getGlobalDebugObject(); + if (dbgGlobal) { + let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script; + if (script) { + grip.location = { + url: script.url, + line: script.startLine + }; + } + } + + return true; + }], + + RegExp: [function({obj, hooks}, grip) { + // Avoid having any special preview for the RegExp.prototype itself. + if (!obj.proto || obj.proto.class != "RegExp") { + return false; + } + + let str = RegExp.prototype.toString.call(obj.unsafeDereference()); + grip.displayString = hooks.createValueGrip(str); + return true; + }], + + Date: [function({obj, hooks}, grip) { + let time = Date.prototype.getTime.call(obj.unsafeDereference()); + + grip.preview = { + timestamp: hooks.createValueGrip(time), + }; + return true; + }], + + Array: [function({obj, hooks}, grip) { + let length = DevToolsUtils.getProperty(obj, "length"); + if (typeof length != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: length, + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let raw = obj.unsafeDereference(); + let items = grip.preview.items = []; + + for (let i = 0; i < length; ++i) { + // Array Xrays filter out various possibly-unsafe properties (like + // functions, and claim that the value is undefined instead. This + // is generally the right thing for privileged code accessing untrusted + // objects, but quite confusing for Object previews. So we manually + // override this protection by waiving Xrays on the array, and re-applying + // Xrays on any indexed value props that we pull off of it. + let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i); + if (desc && !desc.get && !desc.set) { + let value = Cu.unwaiveXrays(desc.value); + value = makeDebuggeeValueIfNeeded(obj, value); + items.push(hooks.createValueGrip(value)); + } else { + items.push(null); + } + + if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + return true; + }], + + Set: [function({obj, hooks}, grip) { + let size = DevToolsUtils.getProperty(obj, "size"); + if (typeof size != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: size, + }; + + // Avoid recursive object grips. + if (hooks.getGripDepth() > 1) { + return true; + } + + let raw = obj.unsafeDereference(); + let items = grip.preview.items = []; + // We currently lack XrayWrappers for Set, so when we iterate over + // the values, the temporary iterator objects get created in the target + // compartment. However, we _do_ have Xrays to Object now, so we end up + // Xraying those temporary objects, and filtering access to |it.value| + // based on whether or not it's Xrayable and/or callable, which breaks + // the for/of iteration. + // + // This code is designed to handle untrusted objects, so we can safely + // waive Xrays on the iterable, and relying on the Debugger machinery to + // make sure we handle the resulting objects carefully. + for (let item of Cu.waiveXrays(Set.prototype.values.call(raw))) { + item = Cu.unwaiveXrays(item); + item = makeDebuggeeValueIfNeeded(obj, item); + items.push(hooks.createValueGrip(item)); + if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + return true; + }], + + Map: [function({obj, hooks}, grip) { + let size = DevToolsUtils.getProperty(obj, "size"); + if (typeof size != "number") { + return false; + } + + grip.preview = { + kind: "MapLike", + size: size, + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let raw = obj.unsafeDereference(); + let entries = grip.preview.entries = []; + // Iterating over a Map via .entries goes through various intermediate + // objects - an Iterator object, then a 2-element Array object, then the + // actual values we care about. We don't have Xrays to Iterator objects, + // so we get Opaque wrappers for them. And even though we have Xrays to + // Arrays, the semantics often deny access to the entires based on the + // nature of the values. So we need waive Xrays for the iterator object + // and the tupes, and then re-apply them on the underlying values until + // we fix bug 1023984. + // + // Even then though, we might want to continue waiving Xrays here for the + // same reason we do so for Arrays above - this filtering behavior is likely + // to be more confusing than beneficial in the case of Object previews. + for (let keyValuePair of Cu.waiveXrays(Map.prototype.entries.call(raw))) { + let key = Cu.unwaiveXrays(keyValuePair[0]); + let value = Cu.unwaiveXrays(keyValuePair[1]); + key = makeDebuggeeValueIfNeeded(obj, key); + value = makeDebuggeeValueIfNeeded(obj, value); + entries.push([hooks.createValueGrip(key), + hooks.createValueGrip(value)]); + if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + return true; + }], + + DOMStringMap: [function({obj, hooks}, grip, rawObj) { + if (!rawObj) { + return false; + } + + let keys = obj.getOwnPropertyNames(); + grip.preview = { + kind: "MapLike", + size: keys.length, + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let entries = grip.preview.entries = []; + for (let key of keys) { + let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]); + entries.push([key, hooks.createValueGrip(value)]); + if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + return true; + }], +}; + +/** + * Generic previewer for "simple" classes like String, Number and Boolean. + * + * @param string className + * Class name to expect. + * @param object classObj + * The class to expect, eg. String. The valueOf() method of the class is + * invoked on the given object. + * @param Debugger.Object obj + * The debugger object we need to preview. + * @param object hooks + * The thread actor to use to create a value grip. + * @return object|null + * An object with one property, "value", which holds the value grip that + * represents the given object. Null is returned if we cant preview the + * object. + */ +function genericObjectPreviewer(className, classObj, obj, hooks) { + if (!obj.proto || obj.proto.class != className) { + return null; + } + + let raw = obj.unsafeDereference(); + let v = null; + try { + v = classObj.prototype.valueOf.call(raw); + } catch (ex) { + // valueOf() can throw if the raw JS object is "misbehaved". + return null; + } + + if (v !== null) { + v = hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v)); + return { value: v }; + } + + return null; +} + +// Preview functions that do not rely on the object class. +DebuggerServer.ObjectActorPreviewers.Object = [ + function TypedArray({obj, hooks}, grip) { + if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) { + return false; + } + + let length = DevToolsUtils.getProperty(obj, "length"); + if (typeof length != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: length, + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let raw = obj.unsafeDereference(); + let global = Cu.getGlobalForObject(DebuggerServer); + let classProto = global[obj.class].prototype; + // The Xray machinery for TypedArrays denies indexed access on the grounds + // that it's slow, and advises callers to do a structured clone instead. + let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0, + OBJECT_PREVIEW_MAX_ITEMS), global); + let items = grip.preview.items = []; + for (let i = 0; i < safeView.length; i++) { + items.push(safeView[i]); + } + + return true; + }, + + function Error({obj, hooks}, grip) { + switch (obj.class) { + case "Error": + case "EvalError": + case "RangeError": + case "ReferenceError": + case "SyntaxError": + case "TypeError": + case "URIError": + let name = DevToolsUtils.getProperty(obj, "name"); + let msg = DevToolsUtils.getProperty(obj, "message"); + let stack = DevToolsUtils.getProperty(obj, "stack"); + let fileName = DevToolsUtils.getProperty(obj, "fileName"); + let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber"); + let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber"); + grip.preview = { + kind: "Error", + name: hooks.createValueGrip(name), + message: hooks.createValueGrip(msg), + stack: hooks.createValueGrip(stack), + fileName: hooks.createValueGrip(fileName), + lineNumber: hooks.createValueGrip(lineNumber), + columnNumber: hooks.createValueGrip(columnNumber), + }; + return true; + default: + return false; + } + }, + + function CSSMediaRule({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSMediaRule)) { + return false; + } + grip.preview = { + kind: "ObjectWithText", + text: hooks.createValueGrip(rawObj.conditionText), + }; + return true; + }, + + function CSSStyleRule({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleRule)) { + return false; + } + grip.preview = { + kind: "ObjectWithText", + text: hooks.createValueGrip(rawObj.selectorText), + }; + return true; + }, + + function ObjectWithURL({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSImportRule || + rawObj instanceof Ci.nsIDOMCSSStyleSheet || + rawObj instanceof Ci.nsIDOMLocation || + rawObj instanceof Ci.nsIDOMWindow)) { + return false; + } + + let url; + if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) { + url = rawObj.location.href; + } else if (rawObj.href) { + url = rawObj.href; + } else { + return false; + } + + grip.preview = { + kind: "ObjectWithURL", + url: hooks.createValueGrip(url), + }; + + return true; + }, + + function ArrayLike({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || + obj.class != "DOMStringList" && + obj.class != "DOMTokenList" && + !(rawObj instanceof Ci.nsIDOMMozNamedAttrMap || + rawObj instanceof Ci.nsIDOMCSSRuleList || + rawObj instanceof Ci.nsIDOMCSSValueList || + rawObj instanceof Ci.nsIDOMFileList || + rawObj instanceof Ci.nsIDOMFontFaceList || + rawObj instanceof Ci.nsIDOMMediaList || + rawObj instanceof Ci.nsIDOMNodeList || + rawObj instanceof Ci.nsIDOMStyleSheetList)) { + return false; + } + + if (typeof rawObj.length != "number") { + return false; + } + + grip.preview = { + kind: "ArrayLike", + length: rawObj.length, + }; + + if (hooks.getGripDepth() > 1) { + return true; + } + + let items = grip.preview.items = []; + + for (let i = 0; i < rawObj.length && + items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) { + let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]); + items.push(hooks.createValueGrip(value)); + } + + return true; + }, + + function CSSStyleDeclaration({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || + !(rawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) { + return false; + } + + grip.preview = { + kind: "MapLike", + size: rawObj.length, + }; + + let entries = grip.preview.entries = []; + + for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS && + i < rawObj.length; i++) { + let prop = rawObj[i]; + let value = rawObj.getPropertyValue(prop); + entries.push([prop, hooks.createValueGrip(value)]); + } + + return true; + }, + + function DOMNode({obj, hooks}, grip, rawObj) { + if (isWorker || obj.class == "Object" || !rawObj || + !(rawObj instanceof Ci.nsIDOMNode)) { + return false; + } + + let preview = grip.preview = { + kind: "DOMNode", + nodeType: rawObj.nodeType, + nodeName: rawObj.nodeName, + }; + + if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) { + preview.location = hooks.createValueGrip(rawObj.location.href); + } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) { + preview.childNodesLength = rawObj.childNodes.length; + + if (hooks.getGripDepth() < 2) { + preview.childNodes = []; + for (let node of rawObj.childNodes) { + let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node)); + preview.childNodes.push(actor); + if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + } + } else if (rawObj instanceof Ci.nsIDOMElement) { + // Add preview for DOM element attributes. + if (rawObj instanceof Ci.nsIDOMHTMLElement) { + preview.nodeName = preview.nodeName.toLowerCase(); + } + + let i = 0; + preview.attributes = {}; + preview.attributesLength = rawObj.attributes.length; + for (let attr of rawObj.attributes) { + preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value); + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + } else if (rawObj instanceof Ci.nsIDOMAttr) { + preview.value = hooks.createValueGrip(rawObj.value); + } else if (rawObj instanceof Ci.nsIDOMText || + rawObj instanceof Ci.nsIDOMComment) { + preview.textContent = hooks.createValueGrip(rawObj.textContent); + } + + return true; + }, + + function DOMEvent({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) { + return false; + } + + let preview = grip.preview = { + kind: "DOMEvent", + type: rawObj.type, + properties: Object.create(null), + }; + + if (hooks.getGripDepth() < 2) { + let target = obj.makeDebuggeeValue(rawObj.target); + preview.target = hooks.createValueGrip(target); + } + + let props = []; + if (rawObj instanceof Ci.nsIDOMMouseEvent) { + props.push("buttons", "clientX", "clientY", "layerX", "layerY"); + } else if (rawObj instanceof Ci.nsIDOMKeyEvent) { + let modifiers = []; + if (rawObj.altKey) { + modifiers.push("Alt"); + } + if (rawObj.ctrlKey) { + modifiers.push("Control"); + } + if (rawObj.metaKey) { + modifiers.push("Meta"); + } + if (rawObj.shiftKey) { + modifiers.push("Shift"); + } + preview.eventKind = "key"; + preview.modifiers = modifiers; + + props.push("key", "charCode", "keyCode"); + } else if (rawObj instanceof Ci.nsIDOMTransitionEvent) { + props.push("propertyName", "pseudoElement"); + } else if (rawObj instanceof Ci.nsIDOMAnimationEvent) { + props.push("animationName", "pseudoElement"); + } else if (rawObj instanceof Ci.nsIDOMClipboardEvent) { + props.push("clipboardData"); + } + + // Add event-specific properties. + for (let prop of props) { + let value = rawObj[prop]; + if (value && (typeof value == "object" || typeof value == "function")) { + // Skip properties pointing to objects. + if (hooks.getGripDepth() > 1) { + continue; + } + value = obj.makeDebuggeeValue(value); + } + preview.properties[prop] = hooks.createValueGrip(value); + } + + // Add any properties we find on the event object. + if (!props.length) { + let i = 0; + for (let prop in rawObj) { + let value = rawObj[prop]; + if (prop == "target" || prop == "type" || value === null || + typeof value == "function") { + continue; + } + if (value && typeof value == "object") { + if (hooks.getGripDepth() > 1) { + continue; + } + value = obj.makeDebuggeeValue(value); + } + preview.properties[prop] = hooks.createValueGrip(value); + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + } + + return true; + }, + + function DOMException({obj, hooks}, grip, rawObj) { + if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) { + return false; + } + + grip.preview = { + kind: "DOMException", + name: hooks.createValueGrip(rawObj.name), + message: hooks.createValueGrip(rawObj.message), + code: hooks.createValueGrip(rawObj.code), + result: hooks.createValueGrip(rawObj.result), + filename: hooks.createValueGrip(rawObj.filename), + lineNumber: hooks.createValueGrip(rawObj.lineNumber), + columnNumber: hooks.createValueGrip(rawObj.columnNumber), + }; + + return true; + }, + + function PseudoArray({obj, hooks}, grip, rawObj) { + let length = 0; + + // Making sure all keys are numbers from 0 to length-1 + let keys = obj.getOwnPropertyNames(); + if (keys.length == 0) { + return false; + } + for (let key of keys) { + if (isNaN(key) || key != length++) { + return false; + } + } + + grip.preview = { + kind: "ArrayLike", + length: length, + }; + + // Avoid recursive object grips. + if (hooks.getGripDepth() > 1) { + return true; + } + + let items = grip.preview.items = []; + + let i = 0; + for (let key of keys) { + if (rawObj.hasOwnProperty(key) && i++ < OBJECT_PREVIEW_MAX_ITEMS) { + let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]); + items.push(hooks.createValueGrip(value)); + } + } + + return true; + }, + + function GenericObject(objectActor, grip) { + let {obj, hooks} = objectActor; + if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) { + return false; + } + + let i = 0, names = []; + let preview = grip.preview = { + kind: "Object", + ownProperties: Object.create(null), + }; + + try { + names = obj.getOwnPropertyNames(); + } catch (ex) { + // Calling getOwnPropertyNames() on some wrapped native prototypes is not + // allowed: "cannot modify properties of a WrappedNative". See bug 952093. + } + + preview.ownPropertiesLength = names.length; + + for (let name of names) { + let desc = objectActor._propertyDescriptor(name, true); + if (!desc) { + continue; + } + + preview.ownProperties[name] = desc; + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { + break; + } + } + + if (i < OBJECT_PREVIEW_MAX_ITEMS) { + preview.safeGetterValues = objectActor._findSafeGetterValues( + Object.keys(preview.ownProperties), + OBJECT_PREVIEW_MAX_ITEMS - i); + } + + return true; + }, +]; + +/** + * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap + * the resulting `value` or `reason` properties in a Debugger.Object instance. + * + * See dom/webidl/PromiseDebugging.webidl + * + * @returns Object + * An object of one of the following forms: + * - { state: "pending" } + * - { state: "fulfilled", value } + * - { state: "rejected", reason } + */ +function getPromiseState(obj) { + if (obj.class != "Promise") { + throw new Error( + "Can't call `getPromiseState` on `Debugger.Object`s that don't " + + "refer to Promise objects."); + } + + const state = PromiseDebugging.getState(obj.unsafeDereference()); + return { + state: state.state, + value: obj.makeDebuggeeValue(state.value), + reason: obj.makeDebuggeeValue(state.reason) + }; +}; + +/** + * Determine if a given value is non-primitive. + * + * @param Any value + * The value to test. + * @return Boolean + * Whether the value is non-primitive. + */ +function isObject(value) { + const type = typeof value; + return type == "object" ? value !== null : type == "function"; +} + +/** + * Create a function that can safely stringify Debugger.Objects of a given + * builtin type. + * + * @param Function ctor + * The builtin class constructor. + * @return Function + * The stringifier for the class. + */ +function createBuiltinStringifier(ctor) { + return obj => ctor.prototype.toString.call(obj.unsafeDereference()); +} + +/** + * Stringify a Debugger.Object-wrapped Error instance. + * + * @param Debugger.Object obj + * The object to stringify. + * @return String + * The stringification of the object. + */ +function errorStringify(obj) { + let name = DevToolsUtils.getProperty(obj, "name"); + if (name === "" || name === undefined) { + name = obj.class; + } else if (isObject(name)) { + name = stringify(name); + } + + let message = DevToolsUtils.getProperty(obj, "message"); + if (isObject(message)) { + message = stringify(message); + } + + if (message === "" || message === undefined) { + return name; + } + return name + ": " + message; +} + +/** + * Stringify a Debugger.Object based on its class. + * + * @param Debugger.Object obj + * The object to stringify. + * @return String + * The stringification for the object. + */ +function stringify(obj) { + if (obj.class == "DeadObject") { + const error = new Error("Dead object encountered."); + DevToolsUtils.reportException("stringify", error); + return ""; + } + + const stringifier = stringifiers[obj.class] || stringifiers.Object; + + try { + return stringifier(obj); + } catch (e) { + DevToolsUtils.reportException("stringify", e); + return ""; + } +} + +// Used to prevent infinite recursion when an array is found inside itself. +var seen = null; + +var stringifiers = { + Error: errorStringify, + EvalError: errorStringify, + RangeError: errorStringify, + ReferenceError: errorStringify, + SyntaxError: errorStringify, + TypeError: errorStringify, + URIError: errorStringify, + Boolean: createBuiltinStringifier(Boolean), + Function: createBuiltinStringifier(Function), + Number: createBuiltinStringifier(Number), + RegExp: createBuiltinStringifier(RegExp), + String: createBuiltinStringifier(String), + Object: obj => "[object " + obj.class + "]", + Array: obj => { + // If we're at the top level then we need to create the Set for tracking + // previously stringified arrays. + const topLevel = !seen; + if (topLevel) { + seen = new Set(); + } else if (seen.has(obj)) { + return ""; + } + + seen.add(obj); + + const len = DevToolsUtils.getProperty(obj, "length"); + let string = ""; + + // The following check is only required because the debuggee could possibly + // be a Proxy and return any value. For normal objects, array.length is + // always a non-negative integer. + if (typeof len == "number" && len > 0) { + for (let i = 0; i < len; i++) { + const desc = obj.getOwnPropertyDescriptor(i); + if (desc) { + const { value } = desc; + if (value != null) { + string += isObject(value) ? stringify(value) : value; + } + } + + if (i < len - 1) { + string += ","; + } + } + } + + if (topLevel) { + seen = null; + } + + return string; + }, + DOMException: obj => { + const message = DevToolsUtils.getProperty(obj, "message") || ""; + const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16); + const code = DevToolsUtils.getProperty(obj, "code"); + const name = DevToolsUtils.getProperty(obj, "name") || ""; + + return '[Exception... "' + message + '" ' + + 'code: "' + code +'" ' + + 'nsresult: "0x' + result + ' (' + name + ')"]'; + }, + Promise: obj => { + const { state, value, reason } = getPromiseState(obj); + let statePreview = state; + if (state != "pending") { + const settledValue = state === "fulfilled" ? value : reason; + statePreview += ": " + (typeof settledValue === "object" && settledValue !== null + ? stringify(settledValue) + : settledValue); + } + return "Promise (" + statePreview + ")"; + }, +}; + +/** + * Make a debuggee value for the given object, if needed. Primitive values + * are left the same. + * + * Use case: you have a raw JS object (after unsafe dereference) and you want to + * send it to the client. In that case you need to use an ObjectActor which + * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue() + * method works only for JS objects and functions. + * + * @param Debugger.Object obj + * @param any value + * @return object + */ +function makeDebuggeeValueIfNeeded(obj, value) { + if (value && (typeof value == "object" || typeof value == "function")) { + return obj.makeDebuggeeValue(value); + } + return value; +} + +/** + * Creates an actor for the specied "very long" string. "Very long" is specified + * at the server's discretion. + * + * @param string String + * The string. + */ +function LongStringActor(string) { + this.string = string; + this.stringLength = string.length; +} + +LongStringActor.prototype = { + actorPrefix: "longString", + + disconnect: function() { + // Because longStringActors is not a weak map, we won't automatically leave + // it so we need to manually leave on disconnect so that we don't leak + // memory. + this._releaseActor(); + }, + + /** + * Returns a grip for this actor for returning in a protocol message. + */ + grip: function() { + return { + "type": "longString", + "initial": this.string.substring( + 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH), + "length": this.stringLength, + "actor": this.actorID + }; + }, + + /** + * Handle a request to extract part of this actor's string. + * + * @param request object + * The protocol request object. + */ + onSubstring: function(request) { + return { + "from": this.actorID, + "substring": this.string.substring(request.start, request.end) + }; + }, + + /** + * Handle a request to release this LongStringActor instance. + */ + onRelease: function () { + // TODO: also check if registeredPool === threadActor.threadLifetimePool + // when the web console moves aray from manually releasing pause-scoped + // actors. + this._releaseActor(); + this.registeredPool.removeActor(this); + return {}; + }, + + _releaseActor: function() { + if (this.registeredPool && this.registeredPool.longStringActors) { + delete this.registeredPool.longStringActors[this.string]; + } + } +}; + +LongStringActor.prototype.requestTypes = { + "substring": LongStringActor.prototype.onSubstring, + "release": LongStringActor.prototype.onRelease +}; + +/** + * Create a grip for the given debuggee value. If the value is an + * object, will create an actor with the given lifetime. + */ +function createValueGrip(value, pool, makeObjectGrip) { + switch (typeof value) { + case "boolean": + return value; + + case "string": + if (stringIsLong(value)) { + return longStringGrip(value, pool); + } + return value; + + case "number": + if (value === Infinity) { + return { type: "Infinity" }; + } else if (value === -Infinity) { + return { type: "-Infinity" }; + } else if (Number.isNaN(value)) { + return { type: "NaN" }; + } else if (!value && 1 / value === -Infinity) { + return { type: "-0" }; + } + return value; + + case "undefined": + return { type: "undefined" }; + + case "object": + if (value === null) { + return { type: "null" }; + } + else if (value.optimizedOut || + value.uninitialized || + value.missingArguments) { + // The slot is optimized out, an uninitialized binding, or + // arguments on a dead scope + return { + type: "null", + optimizedOut: value.optimizedOut, + uninitialized: value.uninitialized, + missingArguments: value.missingArguments + }; + } + return makeObjectGrip(value, pool); + + case "symbol": + let form = { + type: "symbol" + }; + let name = getSymbolName(value); + if (name !== undefined) { + form.name = createValueGrip(name, pool, makeObjectGrip); + } + return form; + + default: + dbg_assert(false, "Failed to provide a grip for: " + value); + return null; + } +} + +// const symbolProtoToString = Symbol.prototype.toString; + +// function getSymbolName(symbol) { +// const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1); +// return name || undefined; +// } + +/** + * Returns true if the string is long enough to use a LongStringActor instead + * of passing the value directly over the protocol. + * + * @param str String + * The string we are checking the length of. + */ +function stringIsLong(str) { + return str.length >= DebuggerServer.LONG_STRING_LENGTH; +} + +/** + * Create a grip for the given string. + * + * @param str String + * The string we are creating a grip for. + * @param pool ActorPool + * The actor pool where the new actor will be added. + */ +function longStringGrip(str, pool) { + if (!pool.longStringActors) { + pool.longStringActors = {}; + } + + if (pool.longStringActors.hasOwnProperty(str)) { + return pool.longStringActors[str].grip(); + } + + let actor = new LongStringActor(str); + pool.addActor(actor); + pool.longStringActors[str] = actor; + return actor.grip(); +} + +// exports.ObjectActor = ObjectActor; +// exports.PropertyIteratorActor = PropertyIteratorActor; +// exports.LongStringActor = LongStringActor; +// exports.createValueGrip = createValueGrip; +// exports.stringIsLong = stringIsLong; +// exports.longStringGrip = longStringGrip; diff --git a/cocos/scripting/js-bindings/script/debugger/actors/root.js b/cocos/scripting/js-bindings/script/debugger/actors/root.js index 66b3814fd1..d1e49aefcc 100644 --- a/cocos/scripting/js-bindings/script/debugger/actors/root.js +++ b/cocos/scripting/js-bindings/script/debugger/actors/root.js @@ -1,4 +1,4 @@ -/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=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 @@ -6,79 +6,17 @@ "use strict"; +// const { Cc, Ci, Cu } = require("chrome"); +// const Services = require("Services"); +// const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common"); +// const { DebuggerServer } = require("devtools/server/main"); + +// loader.lazyGetter(this, "ppmm", () => { +// return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster); +// }); + /* Root actor for the remote debugging protocol. */ -/** - * Methods shared between RootActor and BrowserTabActor. - */ - -/** - * Populate |this._extraActors| as specified by |aFactories|, reusing whatever - * actors are already there. Add all actors in the final extra actors table to - * |aPool|. - * - * The root actor and the tab actor use this to instantiate actors that other - * parts of the browser have specified with DebuggerServer.addTabActor antd - * DebuggerServer.addGlobalActor. - * - * @param aFactories - * An object whose own property names are the names of properties to add to - * some reply packet (say, a tab actor grip or the "listTabs" response - * form), and whose own property values are actor constructor functions, as - * documented for addTabActor and addGlobalActor. - * - * @param this - * The BrowserRootActor or BrowserTabActor with which the new actors will - * be associated. It should support whatever API the |aFactories| - * constructor functions might be interested in, as it is passed to them. - * For the sake of CommonCreateExtraActors itself, it should have at least - * the following properties: - * - * - _extraActors - * An object whose own property names are factory table (and packet) - * property names, and whose values are no-argument actor constructors, - * of the sort that one can add to an ActorPool. - * - * - conn - * The DebuggerServerConnection in which the new actors will participate. - * - * - actorID - * The actor's name, for use as the new actors' parentID. - */ -function CommonCreateExtraActors(aFactories, aPool) { - // Walk over global actors added by extensions. - for (let name in aFactories) { - let actor = this._extraActors[name]; - if (!actor) { - actor = aFactories[name].bind(null, this.conn, this); - actor.prototype = aFactories[name].prototype; - actor.parentID = this.actorID; - this._extraActors[name] = actor; - } - aPool.addActor(actor); - } -} - -/** - * Append the extra actors in |this._extraActors|, constructed by a prior call - * to CommonCreateExtraActors, to |aObject|. - * - * @param aObject - * The object to which the extra actors should be added, under the - * property names given in the |aFactories| table passed to - * CommonCreateExtraActors. - * - * @param this - * The BrowserRootActor or BrowserTabActor whose |_extraActors| table we - * should use; see above. - */ -function CommonAppendExtraActors(aObject) { - for (let name in this._extraActors) { - let actor = this._extraActors[name]; - aObject[name] = actor.actorID; - } -} - /** * Create a remote debugging protocol root actor. * @@ -97,6 +35,11 @@ function CommonAppendExtraActors(aObject) { * notifications when the live list's contents change. One actor in * this list must have a true '.selected' property. * + * - addonList: a live list (see below) of addon actors. If present, the + * new root actor supports the 'listAddons' request, providing the live + * list's elements as its addon actors, and sending 'addonListchanged' + * notifications when the live list's contents change. + * * - globalActorFactories: an object |A| describing further actors to * attach to the 'listTabs' reply. This is the type accumulated by * DebuggerServer.addGlobalActor. For each own property |P| of |A|, @@ -118,9 +61,7 @@ function CommonAppendExtraActors(aObject) { * list of actors, and also notifies its clients of changes to the list. A * live list's interface is two properties: * - * - iterator: a method that returns an iterator. A for-of loop will call - * this method to obtain an iterator for the loop, so if LL is - * a live list, one can simply write 'for (i of LL) ...'. + * - getList: a method that returns a promise to the contents of the list. * * - onListChanged: a handler called, with no arguments, when the set of * values the iterator would produce has changed since the last @@ -148,18 +89,92 @@ function CommonAppendExtraActors(aObject) { * actually produce any actors until they are reached in the course of * iteration: alliterative lazy live lists. */ + function RootActor(aConnection, aParameters) { this.conn = aConnection; this._parameters = aParameters; this._onTabListChanged = this.onTabListChanged.bind(this); this._onAddonListChanged = this.onAddonListChanged.bind(this); this._extraActors = {}; + + this._globalActorPool = new ActorPool(this.conn); + this.conn.addActorPool(this._globalActorPool); + + this._chromeActor = null; } RootActor.prototype = { constructor: RootActor, applicationType: "browser", + traits: { + sources: true, + // Whether the inspector actor allows modifying outer HTML. + editOuterHTML: true, + // Whether the inspector actor allows modifying innerHTML and inserting + // adjacent HTML. + pasteHTML: true, + // Whether the server-side highlighter actor exists and can be used to + // remotely highlight nodes (see server/actors/highlighters.js) + highlightable: true, + // Which custom highlighter does the server-side highlighter actor supports? + // (see server/actors/highlighters.js) + customHighlighters: true, + // Whether the inspector actor implements the getImageDataFromURL + // method that returns data-uris for image URLs. This is used for image + // tooltips for instance + urlToImageDataResolver: true, + networkMonitor: true, + // Whether the storage inspector actor to inspect cookies, etc. + storageInspector: true, + // Whether storage inspector is read only + storageInspectorReadOnly: true, + // Whether conditional breakpoints are supported + conditionalBreakpoints: true, + // Whether the server supports full source actors (breakpoints on + // eval scripts, etc) + debuggerSourceActors: true, + bulk: true, + // Whether the style rule actor implements the modifySelector method + // that modifies the rule's selector + selectorEditable: true, + // Whether the page style actor implements the addNewRule method that + // adds new rules to the page + addNewRule: true, + // Whether the dom node actor implements the getUniqueSelector method + getUniqueSelector: true, + // Whether the director scripts are supported + directorScripts: true, + // Whether the debugger server supports + // blackboxing/pretty-printing (not supported in Fever Dream yet) + noBlackBoxing: false, + noPrettyPrinting: false, + // Whether the page style actor implements the getUsedFontFaces method + // that returns the font faces used on a node + getUsedFontFaces: true, + // Trait added in Gecko 38, indicating that all features necessary for + // grabbing allocations from the MemoryActor are available for the performance tool + memoryActorAllocations: true, + // Added in Gecko 40, indicating that the backend isn't stupid about + // sending resumption packets on tab navigation. + noNeedToFakeResumptionOnNavigation: true, + // Added in Firefox 40. Indicates that the backend supports registering custom + // commands through the WebConsoleCommands API. + webConsoleCommands: true, + // Whether root actor exposes tab actors + // if allowChromeProcess is true, you can fetch a ChromeActor instance + // to debug chrome and any non-content ressource via getProcess request + // if allocChromeProcess is defined, but not true, it means that root actor + // no longer expose tab actors, but also that getProcess forbids + // exposing actors for security reasons + get allowChromeProcess() { + return DebuggerServer.allowChromeProcess; + }, + // Whether or not `getProfile()` supports specifying a `startTime` + // and `endTime` to filter out samples. Fx40+ + profilerDataFilterable: true, + }, + /** * Return a 'hello' packet as specified by the Remote Debugging Protocol. */ @@ -169,23 +184,10 @@ RootActor.prototype = { applicationType: this.applicationType, /* This is not in the spec, but it's used by tests. */ testConnectionPrefix: this.conn.prefix, - traits: { - sources: true, - editOuterHTML: true - } + traits: this.traits }; }, - /** - * This is true for the root actor only, used by some child actors - */ - get isRootActor() true, - - /** - * The (chrome) window, for use by child actors - */ - get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType), - /** * Disconnects the actor from the browser window. */ @@ -201,6 +203,11 @@ RootActor.prototype = { this._parameters.onShutdown(); } this._extraActors = null; + this.conn = null; + this._tabActorPool = null; + this._globalActorPool = null; + this._parameters = null; + this._chromeActor = null; }, /* The 'listTabs' request and the 'tabListChanged' notification. */ @@ -235,10 +242,12 @@ RootActor.prototype = { newActorPool.addActor(tabActor); tabActorList.push(tabActor); } - /* DebuggerServer.addGlobalActor support: create actors. */ - this._createExtraActors(this._parameters.globalActorFactories, newActorPool); - + if (!this._globalActorPool) { + this._globalActorPool = new ActorPool(this.conn); + this.conn.addActorPool(this._globalActorPool); + } + // this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool); /* * Drop the old actorID -> actor map. Actors that still mattered were * added to the new map; others will go away. @@ -252,11 +261,16 @@ RootActor.prototype = { let reply = { "from": this.actorID, "selected": selected || 0, - "tabs": [actor.form() for (actor of tabActorList)], + "tabs": tabActorList.map(actor => actor.form()) }; + /* If a root window is accessible, include its URL. */ + if (this.url) { + reply.url = this.url; + } + /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */ - this._appendExtraActors(reply); + // this._appendExtraActors(reply); /* * Now that we're actually going to report the contents of tabList to @@ -269,6 +283,33 @@ RootActor.prototype = { }); }, + onGetTab: function (options) { + let tabList = this._parameters.tabList; + if (!tabList) { + return { error: "noTabs", + message: "This root actor has no browser tabs." }; + } + if (!this._tabActorPool) { + this._tabActorPool = new ActorPool(this.conn); + this.conn.addActorPool(this._tabActorPool); + } + return tabList.getTab(options) + .then(tabActor => { + tabActor.parentID = this.actorID; + this._tabActorPool.addActor(tabActor); + + return { tab: tabActor.form() }; + }, error => { + if (error.error) { + // Pipe expected errors as-is to the client + return error; + } else { + return { error: "noTab", + message: "Unexpected error while calling getTab(): " + error }; + } + }); + }, + onTabListChanged: function () { this.conn.send({ from: this.actorID, type:"tabListChanged" }); /* It's a one-shot notification; no need to watch any more. */ @@ -298,7 +339,7 @@ RootActor.prototype = { return { "from": this.actorID, - "addons": [addonActor.form() for (addonActor of addonActors)] + "addons": addonActors.map(addonActor => addonActor.form()) }; }); }, @@ -308,78 +349,96 @@ RootActor.prototype = { this._parameters.addonList.onListChanged = null; }, + onListProcesses: function () { + let processes = []; + for (let i = 0; i < ppmm.childCount; i++) { + processes.push({ + id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything... + parent: i == 0, // XXX Weak, but appear to be stable + tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this + }); + } + return { processes: processes }; + }, + + onGetProcess: function (aRequest) { + if (!DebuggerServer.allowChromeProcess) { + return { error: "forbidden", + message: "You are not allowed to debug chrome." }; + } + if (("id" in aRequest) && typeof(aRequest.id) != "number") { + return { error: "wrongParameter", + message: "getProcess requires a valid `id` attribute." }; + } + // If the request doesn't contains id parameter or id is 0 + // (id == 0, based on onListProcesses implementation) + if ((!("id" in aRequest)) || aRequest.id === 0) { + if (!this._chromeActor) { + // Create a ChromeActor for the parent process + let { ChromeActor } = require("devtools/server/actors/chrome"); + this._chromeActor = new ChromeActor(this.conn); + this._globalActorPool.addActor(this._chromeActor); + } + + return { form: this._chromeActor.form() }; + } else { + let mm = ppmm.getChildAt(aRequest.id); + if (!mm) { + return { error: "noProcess", + message: "There is no process with id '" + aRequest.id + "'." }; + } + return DebuggerServer.connectToContent(this.conn, mm) + .then(form => ({ form })); + } + }, + /* This is not in the spec, but it's used by tests. */ onEcho: function (aRequest) { /* * Request packets are frozen. Copy aRequest, so that * DebuggerServerConnection.onPacket can attach a 'from' property. */ - return JSON.parse(JSON.stringify(aRequest)); + return Cu.cloneInto(aRequest, {}); + }, + + onProtocolDescription: function () { + return require("devtools/server/protocol").dumpProtocolSpec(); }, /* Support for DebuggerServer.addGlobalActor. */ - _createExtraActors: CommonCreateExtraActors, - _appendExtraActors: CommonAppendExtraActors, - - /* ThreadActor hooks. */ + // _createExtraActors: createExtraActors, + // _appendExtraActors: appendExtraActors, /** - * Prepare to enter a nested event loop by disabling debuggee events. + * Remove the extra actor (added by DebuggerServer.addGlobalActor or + * DebuggerServer.addTabActor) name |aName|. */ - preNest: function() { - // Disable events in all open windows. - let e = windowMediator.getEnumerator(null); - while (e.hasMoreElements()) { - let win = e.getNext(); - let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - windowUtils.suppressEventHandling(true); - windowUtils.suspendTimeouts(); + removeActorByName: function(aName) { + if (aName in this._extraActors) { + const actor = this._extraActors[aName]; + if (this._globalActorPool.has(actor)) { + this._globalActorPool.removeActor(actor); + } + if (this._tabActorPool) { + // Iterate over TabActor instances to also remove tab actors + // created during listTabs for each document. + this._tabActorPool.forEach(tab => { + tab.removeActorByName(aName); + }); + } + delete this._extraActors[aName]; } - }, - - /** - * Prepare to exit a nested event loop by enabling debuggee events. - */ - postNest: function(aNestData) { - // Enable events in all open windows. - let e = windowMediator.getEnumerator(null); - while (e.hasMoreElements()) { - let win = e.getNext(); - let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - windowUtils.resumeTimeouts(); - windowUtils.suppressEventHandling(false); - } - }, - - /* ChromeDebuggerActor hooks. */ - - /** - * Add the specified actor to the default actor pool connection, in order to - * keep it alive as long as the server is. This is used by breakpoints in the - * thread and chrome debugger actors. - * - * @param actor aActor - * The actor object. - */ - addToParentPool: function(aActor) { - this.conn.addActor(aActor); - }, - - /** - * Remove the specified actor from the default actor pool. - * - * @param BreakpointActor aActor - * The actor object. - */ - removeFromParentPool: function(aActor) { - this.conn.removeActor(aActor); - } -} + } +}; RootActor.prototype.requestTypes = { "listTabs": RootActor.prototype.onListTabs, + "getTab": RootActor.prototype.onGetTab, "listAddons": RootActor.prototype.onListAddons, - "echo": RootActor.prototype.onEcho + "listProcesses": RootActor.prototype.onListProcesses, + "getProcess": RootActor.prototype.onGetProcess, + "echo": RootActor.prototype.onEcho, + "protocolDescription": RootActor.prototype.onProtocolDescription }; + +// exports.RootActor = RootActor; diff --git a/cocos/scripting/js-bindings/script/debugger/actors/script.js b/cocos/scripting/js-bindings/script/debugger/actors/script.js index 853fc1ce7a..b6d95002ef 100644 --- a/cocos/scripting/js-bindings/script/debugger/actors/script.js +++ b/cocos/scripting/js-bindings/script/debugger/actors/script.js @@ -1,4 +1,4 @@ -/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=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 @@ -6,274 +6,303 @@ "use strict"; -/** - * BreakpointStore objects keep track of all breakpoints that get set so that we - * can reset them when the same script is introduced to the thread again (such - * as after a refresh). - */ -function BreakpointStore() { - // If we have a whole-line breakpoint set at LINE in URL, then - // - // this._wholeLineBreakpoints[URL][LINE] - // - // is an object - // - // { url, line[, actor] } - // - // where the `actor` property is optional. - this._wholeLineBreakpoints = Object.create(null); +// const Services = require("Services"); +// const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome"); +// const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common"); +// const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object"); +// const { DebuggerServer } = require("devtools/server/main"); +// const DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); +const { dbg_assert, dumpn, update } = DevToolsUtils; +// const { dirname, joinURI } = require("devtools/toolkit/path"); +// const promise = require("promise"); +// const PromiseDebugging = require("PromiseDebugging"); +// const xpcInspector = require("xpcInspector"); +// const ScriptStore = require("./utils/ScriptStore"); +// const { DevToolsWorker } = require("devtools/toolkit/shared/worker.js"); - // If we have a breakpoint set at LINE, COLUMN in URL, then - // - // this._breakpoints[URL][LINE][COLUMN] - // - // is an object - // - // { url, line[, actor] } - // - // where the `actor` property is optional. - this._breakpoints = Object.create(null); +// const { defer, resolve, reject, all } = promise; + +// loader.lazyGetter(this, "Debugger", () => { +// let Debugger = require("Debugger"); +// hackDebugger(Debugger); +// return Debugger; +// }); +// loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true); +// loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true); +// loader.lazyRequireGetter(this, "CssLogic", "devtools/styleinspector/css-logic", true); +// loader.lazyRequireGetter(this, "events", "sdk/event/core"); +// loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id"); +// loader.lazyRequireGetter(this, "setTimeout", "sdk/timers", true); + +var fetch = function(aURL, aOptions={ loadFromCache: true }) { + let deferred = defer(); + let scheme; + let url = aURL.split(" -> ").pop(); + let charset; + let filePath = url; + let contentType; + + // try { + // scheme = Services.io.extractScheme(url); + // } catch (e) { + // In the xpcshell tests, the script url is the absolute path of the test + // file, which will make a malformed URI error be thrown. Add the file + // scheme prefix ourselves. + url = "file://" + url; + // scheme = Services.io.extractScheme(url); + // } + + try { + let jsb = globalDebuggee.jsb; + let fileUtils = jsb.fileUtils; + let source = fileUtils.getStringFromFile(filePath);//NetUtil.readInputStreamToString(aStream, aStream.available()); + if (!source) + { + deferred.reject("Request failed: " + url); + } + else + { + deferred.resolve(source); + } + } catch (ex) { + deferred.reject(ex); + } + + return deferred.promise.then(source => { + return { + // content: convertToUnicode(source, charset), + content: DevToolsUtils.utf8to16(source), + contentType: contentType + }; + }); } -BreakpointStore.prototype = { +/** + * A BreakpointActorMap is a map from locations to instances of BreakpointActor. + */ +function BreakpointActorMap() { + this._size = 0; + this._actors = {}; +} +BreakpointActorMap.prototype = { /** - * Add a breakpoint to the breakpoint store. + * Return the number of BreakpointActors in this BreakpointActorMap. * - * @param Object aBreakpoint - * The breakpoint to be added (not copied). It is an object with the - * following properties: - * - url - * - line - * - column (optional; omission implies that the breakpoint is for - * the whole line) - * - actor (optional) + * @returns Number + * The number of BreakpointActor in this BreakpointActorMap. */ - addBreakpoint: function BS_addBreakpoint(aBreakpoint) { - let { url, line, column } = aBreakpoint; - - if (column != null) { - if (!this._breakpoints[url]) { - this._breakpoints[url] = []; - } - if (!this._breakpoints[url][line]) { - this._breakpoints[url][line] = []; - } - this._breakpoints[url][line][column] = aBreakpoint; - } else { - // Add a breakpoint that breaks on the whole line. - if (!this._wholeLineBreakpoints[url]) { - this._wholeLineBreakpoints[url] = []; - } - this._wholeLineBreakpoints[url][line] = aBreakpoint; - } + get size() { + return this._size; }, /** - * Remove a breakpoint from the breakpoint store. + * Generate all BreakpointActors that match the given location in + * this BreakpointActorMap. * - * @param Object aBreakpoint - * The breakpoint to be removed. It is an object with the following - * properties: - * - url - * - line - * - column (optional) + * @param OriginalLocation location + * The location for which matching BreakpointActors should be generated. */ - removeBreakpoint: function BS_removeBreakpoint({ url, line, column }) { - if (column != null) { - if (this._breakpoints[url]) { - if (this._breakpoints[url][line]) { - delete this._breakpoints[url][line][column]; + findActors: function* (location = new OriginalLocation()) { + // Fast shortcut for when we know we won't find any actors. Surprisingly + // enough, this speeds up refreshing when there are no breakpoints set by + // about 2x! + if (this.size === 0) { + return; + } - // If this was the last breakpoint on this line, delete the line from - // `this._breakpoints[url]` as well. Otherwise `_iterLines` will yield - // this line even though we no longer have breakpoints on - // it. Furthermore, we use Object.keys() instead of just checking - // `this._breakpoints[url].length` directly, because deleting - // properties from sparse arrays doesn't update the `length` property - // like adding them does. - if (Object.keys(this._breakpoints[url][line]).length === 0) { - delete this._breakpoints[url][line]; - } + function* findKeys(object, key) { + if (key !== undefined) { + if (key in object) { + yield key; } } - } else { - if (this._wholeLineBreakpoints[url]) { - delete this._wholeLineBreakpoints[url][line]; + else { + for (let key of Object.keys(object)) { + yield key; + } } } - }, - /** - * Get a breakpoint from the breakpoint store. Will throw an error if the - * breakpoint is not found. - * - * @param Object aLocation - * The location of the breakpoint you are retrieving. It is an object - * with the following properties: - * - url - * - line - * - column (optional) - */ - getBreakpoint: function BS_getBreakpoint(aLocation) { - let { url, line, column } = aLocation; - dbg_assert(url != null); - dbg_assert(line != null); + let query = { + sourceActorID: location.originalSourceActor ? location.originalSourceActor.actorID : undefined, + line: location.originalLine, + }; - var foundBreakpoint = this.hasBreakpoint(aLocation); - if (foundBreakpoint == null) { - throw new Error("No breakpoint at url = " + url - + ", line = " + line - + ", column = " + column); + // If location contains a line, assume we are searching for a whole line + // breakpoint, and set begin/endColumn accordingly. Otherwise, we are + // searching for all breakpoints, so begin/endColumn should be left unset. + if (location.originalLine) { + query.beginColumn = location.originalColumn ? location.originalColumn : 0; + query.endColumn = location.originalColumn ? location.originalColumn + 1 : Infinity; + } else { + query.beginColumn = location.originalColumn ? query.originalColumn : undefined; + query.endColumn = location.originalColumn ? query.originalColumn + 1 : undefined; } - return foundBreakpoint; + for (let sourceActorID of findKeys(this._actors, query.sourceActorID)) + for (let line of findKeys(this._actors[sourceActorID], query.line)) + for (let beginColumn of findKeys(this._actors[sourceActorID][line], query.beginColumn)) + for (let endColumn of findKeys(this._actors[sourceActorID][line][beginColumn], query.endColumn)) { + yield this._actors[sourceActorID][line][beginColumn][endColumn]; + } }, /** - * Checks if the breakpoint store has a requested breakpoint. + * Return the BreakpointActor at the given location in this + * BreakpointActorMap. * - * @param Object aLocation - * The location of the breakpoint you are retrieving. It is an object - * with the following properties: - * - url - * - line - * - column (optional) - * @returns The stored breakpoint if it exists, null otherwise. + * @param OriginalLocation location + * The location for which the BreakpointActor should be returned. + * + * @returns BreakpointActor actor + * The BreakpointActor at the given location. */ - hasBreakpoint: function BS_hasBreakpoint(aLocation) { - let { url, line, column } = aLocation; - dbg_assert(url != null); - dbg_assert(line != null); - for (let bp of this.findBreakpoints(aLocation)) { - // We will get whole line breakpoints before individual columns, so just - // return the first one and if they didn't specify a column then they will - // get the whole line breakpoint, and otherwise we will find the correct - // one. - return bp; + getActor: function (originalLocation) { + for (let actor of this.findActors(originalLocation)) { + return actor; } return null; }, /** - * Iterate over the breakpoints in this breakpoint store. You can optionally - * provide search parameters to filter the set of breakpoints down to those - * that match your parameters. + * Set the given BreakpointActor to the given location in this + * BreakpointActorMap. * - * @param Object aSearchParams - * Optional. An object with the following properties: - * - url - * - line (optional; requires the url property) - * - column (optional; requires the line property) + * @param OriginalLocation location + * The location to which the given BreakpointActor should be set. + * + * @param BreakpointActor actor + * The BreakpointActor to be set to the given location. */ - findBreakpoints: function BS_findBreakpoints(aSearchParams={}) { - if (aSearchParams.column != null) { - dbg_assert(aSearchParams.line != null); - } - if (aSearchParams.line != null) { - dbg_assert(aSearchParams.url != null); - } + setActor: function (location, actor) { + let { originalSourceActor, originalLine, originalColumn } = location; - for (let url of this._iterUrls(aSearchParams.url)) { - for (let line of this._iterLines(url, aSearchParams.line)) { - // Always yield whole line breakpoints first. See comment in - // |BreakpointStore.prototype.hasBreakpoint|. - if (aSearchParams.column == null - && this._wholeLineBreakpoints[url] - && this._wholeLineBreakpoints[url][line]) { - yield this._wholeLineBreakpoints[url][line]; + let sourceActorID = originalSourceActor.actorID; + let line = originalLine; + let beginColumn = originalColumn ? originalColumn : 0; + let endColumn = originalColumn ? originalColumn + 1 : Infinity; + + if (!this._actors[sourceActorID]) { + this._actors[sourceActorID] = []; + } + if (!this._actors[sourceActorID][line]) { + this._actors[sourceActorID][line] = []; + } + if (!this._actors[sourceActorID][line][beginColumn]) { + this._actors[sourceActorID][line][beginColumn] = []; + } + if (!this._actors[sourceActorID][line][beginColumn][endColumn]) { + ++this._size; + } + this._actors[sourceActorID][line][beginColumn][endColumn] = actor; + }, + + /** + * Delete the BreakpointActor from the given location in this + * BreakpointActorMap. + * + * @param OriginalLocation location + * The location from which the BreakpointActor should be deleted. + */ + deleteActor: function (location) { + let { originalSourceActor, originalLine, originalColumn } = location; + + let sourceActorID = originalSourceActor.actorID; + let line = originalLine; + let beginColumn = originalColumn ? originalColumn : 0; + let endColumn = originalColumn ? originalColumn + 1 : Infinity; + + if (this._actors[sourceActorID]) { + if (this._actors[sourceActorID][line]) { + if (this._actors[sourceActorID][line][beginColumn]) { + if (this._actors[sourceActorID][line][beginColumn][endColumn]) { + --this._size; + } + delete this._actors[sourceActorID][line][beginColumn][endColumn]; + if (Object.keys(this._actors[sourceActorID][line][beginColumn]).length === 0) { + delete this._actors[sourceActorID][line][beginColumn]; + } } - for (let column of this._iterColumns(url, line, aSearchParams.column)) { - yield this._breakpoints[url][line][column]; + if (Object.keys(this._actors[sourceActorID][line]).length === 0) { + delete this._actors[sourceActorID][line]; } } } - }, - - _iterUrls: function BS__iterUrls(aUrl) { - if (aUrl) { - if (this._breakpoints[aUrl] || this._wholeLineBreakpoints[aUrl]) { - yield aUrl; - } - } else { - for (let url of Object.keys(this._wholeLineBreakpoints)) { - yield url; - } - for (let url of Object.keys(this._breakpoints)) { - if (url in this._wholeLineBreakpoints) { - continue; - } - yield url; - } - } - }, - - _iterLines: function BS__iterLines(aUrl, aLine) { - if (aLine != null) { - if ((this._wholeLineBreakpoints[aUrl] - && this._wholeLineBreakpoints[aUrl][aLine]) - || (this._breakpoints[aUrl] && this._breakpoints[aUrl][aLine])) { - yield aLine; - } - } else { - const wholeLines = this._wholeLineBreakpoints[aUrl] - ? Object.keys(this._wholeLineBreakpoints[aUrl]) - : []; - const columnLines = this._breakpoints[aUrl] - ? Object.keys(this._breakpoints[aUrl]) - : []; - - const lines = wholeLines.concat(columnLines).sort(); - - let lastLine; - for (let line of lines) { - if (line === lastLine) { - continue; - } - yield line; - lastLine = line; - } - } - }, - - _iterColumns: function BS__iterColumns(aUrl, aLine, aColumn) { - if (!this._breakpoints[aUrl] || !this._breakpoints[aUrl][aLine]) { - return; - } - - if (aColumn != null) { - if (this._breakpoints[aUrl][aLine][aColumn]) { - yield aColumn; - } - } else { - for (let column in this._breakpoints[aUrl][aLine]) { - yield column; - } - } - }, + } }; +exports.BreakpointActorMap = BreakpointActorMap; + +/** + * Keeps track of persistent sources across reloads and ties different + * source instances to the same actor id so that things like + * breakpoints survive reloads. ThreadSources uses this to force the + * same actorID on a SourceActor. + */ +function SourceActorStore() { + // source identifier --> actor id + this._sourceActorIds = Object.create(null); +} + +SourceActorStore.prototype = { + /** + * Lookup an existing actor id that represents this source, if available. + */ + getReusableActorId: function(aSource, aOriginalUrl) { + let url = this.getUniqueKey(aSource, aOriginalUrl); + if (url && url in this._sourceActorIds) { + return this._sourceActorIds[url]; + } + return null; + }, + + /** + * Update a source with an actorID. + */ + setReusableActorId: function(aSource, aOriginalUrl, actorID) { + let url = this.getUniqueKey(aSource, aOriginalUrl); + if (url) { + this._sourceActorIds[url] = actorID; + } + }, + + /** + * Make a unique URL from a source that identifies it across reloads. + */ + getUniqueKey: function(aSource, aOriginalUrl) { + if (aOriginalUrl) { + // Original source from a sourcemap. + return aOriginalUrl; + } + else { + return getSourceURL(aSource); + } + } +}; + +exports.SourceActorStore = SourceActorStore; + /** * Manages pushing event loops and automatically pops and exits them in the * correct order as they are resolved. * - * @param nsIJSInspector inspector - * The underlying JS inspector we use to enter and exit nested event - * loops. + * @param ThreadActor thread + * The thread actor instance that owns this EventLoopStack. + * @param DebuggerServerConnection connection + * The remote protocol connection associated with this event loop stack. * @param Object hooks * An object with the following properties: * - url: The URL string of the debuggee we are spinning an event loop * for. * - preNest: function called before entering a nested event loop * - postNest: function called after exiting a nested event loop - * @param ThreadActor thread - * The thread actor instance that owns this EventLoopStack. */ -function EventLoopStack({ inspector, thread, hooks }) { - this._inspector = inspector; +function EventLoopStack({ thread, connection, hooks }) { this._hooks = hooks; this._thread = thread; + this._connection = connection; } EventLoopStack.prototype = { @@ -281,7 +310,8 @@ EventLoopStack.prototype = { * The number of nested event loops on the stack. */ get size() { - return this._inspector.eventLoopNestLevel(); + // return xpcInspector.eventLoopNestLevel; + return _getEventLoopNestLevel(); }, /** @@ -291,7 +321,7 @@ EventLoopStack.prototype = { let url = null; if (this.size > 0) { try { - url = this._inspector.lastNestRequestor.url + url = xpcInspector.lastNestRequestor.url } catch (e) { // The tab's URL getter may throw if the tab is destroyed by the time // this code runs, but we don't really care at this point. @@ -301,6 +331,14 @@ EventLoopStack.prototype = { return url; }, + /** + * The DebuggerServerConnection of the debugger who pushed the event loop on + * top of the stack + */ + get lastConnection() { + return xpcInspector.lastNestRequestor._connection; + }, + /** * Push a new nested event loop onto the stack. * @@ -308,8 +346,8 @@ EventLoopStack.prototype = { */ push: function () { return new EventLoop({ - inspector: this._inspector, thread: this._thread, + connection: this._connection, hooks: this._hooks }); } @@ -319,18 +357,18 @@ EventLoopStack.prototype = { * An object that represents a nested event loop. It is used as the nest * requestor with nsIJSInspector instances. * - * @param nsIJSInspector inspector - * The JS Inspector that runs nested event loops. * @param ThreadActor thread * The thread actor that is creating this nested event loop. + * @param DebuggerServerConnection connection + * The remote protocol connection associated with this event loop. * @param Object hooks * The same hooks object passed into EventLoopStack during its * initialization. */ -function EventLoop({ inspector, thread, hooks }) { - this._inspector = inspector; +function EventLoop({ thread, connection, hooks }) { this._thread = thread; this._hooks = hooks; + this._connection = connection; this.enter = this.enter.bind(this); this.resolve = this.resolve.bind(this); @@ -350,14 +388,14 @@ EventLoop.prototype = { : null; this.entered = true; - this._inspector.enterNestedEventLoop(this); + // xpcInspector.enterNestedEventLoop(this); + _enterNestedEventLoop(); // Keep exiting nested event loops while the last requestor is resolved. - // James commented. - // if (this._inspector.eventLoopNestLevel() > 0) { - // const { resolved } = this._inspector.lastNestRequestor; + // if (xpcInspector.eventLoopNestLevel > 0) { + // const { resolved } = xpcInspector.lastNestRequestor; // if (resolved) { - // this._inspector.exitNestedEventLoop(); + // xpcInspector.exitNestedEventLoop(); // } // } @@ -385,9 +423,10 @@ EventLoop.prototype = { throw new Error("Already resolved this nested event loop!"); } this.resolved = true; - // James commented. if (this === this._inspector.lastNestRequestor) + // if (this === xpcInspector.lastNestRequestor) { - this._inspector.exitNestedEventLoop(); + // xpcInspector.exitNestedEventLoop(); + _exitNestedEventLoop(); return true; } return false; @@ -397,60 +436,104 @@ EventLoop.prototype = { /** * JSD2 actors. */ + /** * Creates a ThreadActor. * * ThreadActors manage a JSInspector object and manage execution/inspection * of debuggees. * - * @param aHooks object - * An object with preNest and postNest methods for calling when entering - * and exiting a nested event loop, addToParentPool and - * removeFromParentPool methods for handling the lifetime of actors that - * will outlive the thread, like breakpoints. + * @param aParent object + * This |ThreadActor|'s parent actor. It must implement the following + * properties: + * - url: The URL string of the debuggee. + * - window: The global window object. + * - preNest: Function called before entering a nested event loop. + * - postNest: Function called after exiting a nested event loop. + * - makeDebugger: A function that takes no arguments and instantiates + * a Debugger that manages its globals on its own. * @param aGlobal object [optional] * An optional (for content debugging only) reference to the content * window. */ -function ThreadActor(aHooks, aGlobal) +function ThreadActor(aParent, aGlobal) { this._state = "detached"; this._frameActors = []; - this._hooks = aHooks; - this.global = aGlobal; - this._nestedEventLoops = new EventLoopStack({ - inspector: DebuggerServer.xpcInspector, - hooks: aHooks, - thread: this - }); - // A map of actorID -> actor for breakpoints created and managed by the server. - this._hiddenBreakpoints = new Map(); - - this.findGlobals = this.globalManager.findGlobals.bind(this); - this.onNewGlobal = this.globalManager.onNewGlobal.bind(this); - this.onNewSource = this.onNewSource.bind(this); - this._allEventsListener = this._allEventsListener.bind(this); + this._parent = aParent; + this._dbg = null; + this._gripDepth = 0; + this._threadLifetimePool = null; + this._tabClosed = false; + this._scripts = null; + this._pauseOnDOMEvents = null; this._options = { - useSourceMaps: false + useSourceMaps: false, + autoBlackBox: false }; + + this.breakpointActorMap = new BreakpointActorMap(); + this.sourceActorStore = new SourceActorStore(); + + this._debuggerSourcesSeen = null; + + // A map of actorID -> actor for breakpoints created and managed by the + // server. + this._hiddenBreakpoints = new Map(); + + this.global = aGlobal; + + this._allEventsListener = this._allEventsListener.bind(this); + this.onNewGlobal = this.onNewGlobal.bind(this); + this.onNewSource = this.onNewSource.bind(this); + this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this); + this.onDebuggerStatement = this.onDebuggerStatement.bind(this); + this.onNewScript = this.onNewScript.bind(this); + this.objectGrip = this.objectGrip.bind(this); + this.pauseObjectGrip = this.pauseObjectGrip.bind(this); + this._onWindowReady = this._onWindowReady.bind(this); + // events.on(this._parent, "window-ready", this._onWindowReady); + // Set a wrappedJSObject property so |this| can be sent via the observer svc + // for the xpcshell harness. + this.wrappedJSObject = this; } -/** - * The breakpoint store must be shared across instances of ThreadActor so that - * page reloads don't blow away all of our breakpoints. - */ -ThreadActor.breakpointStore = new BreakpointStore(); - ThreadActor.prototype = { + // Used by the ObjectActor to keep track of the depth of grip() calls. + _gripDepth: null, + actorPrefix: "context", - get state() { return this._state; }, - get attached() this.state == "attached" || - this.state == "running" || - this.state == "paused", + get dbg() { + if (!this._dbg) { + this._dbg = this._parent.makeDebugger(); + this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook; + this._dbg.onDebuggerStatement = this.onDebuggerStatement; + this._dbg.onNewScript = this.onNewScript; + // this._dbg.on("newGlobal", this.onNewGlobal); + // Keep the debugger disabled until a client attaches. + this._dbg.enabled = this._state != "detached"; + } + return this._dbg; + }, - get breakpointStore() { return ThreadActor.breakpointStore; }, + get globalDebugObject() { + if (!this._parent.window) { + return null; + } + return this.dbg.makeGlobalObjectReference(this._parent.window); + }, + + get state() { + return this._state; + }, + + get attached() { + return this.state == "attached" || + this.state == "running" || + this.state == "paused"; + }, get threadLifetimePool() { if (!this._threadLifetimePool) { @@ -461,16 +544,20 @@ ThreadActor.prototype = { return this._threadLifetimePool; }, - get sources() { - if (!this._sources) { - this._sources = new ThreadSources(this, this._options.useSourceMaps, - this._allowSource, this.onNewSource); + get scripts() { + if (!this._scripts) { + this._scripts = new ScriptStore(); + this._scripts.addScripts(this.dbg.findScripts()); } - return this._sources; + return this._scripts; + }, + + get sources() { + return this._parent.sources; }, get youngestFrame() { - if (!this.state == "paused") { + if (this.state != "paused") { return null; } return this.dbg.getNewestFrame(); @@ -479,35 +566,15 @@ ThreadActor.prototype = { _prettyPrintWorker: null, get prettyPrintWorker() { if (!this._prettyPrintWorker) { - this._prettyPrintWorker = new ChromeWorker( - "resource://gre/modules/devtools/server/actors/pretty-print-worker.js"); - - this._prettyPrintWorker.addEventListener( - "error", this._onPrettyPrintError, false); - - if (wantLogging) { - this._prettyPrintWorker.addEventListener("message", this._onPrettyPrintMsg, false); - - const postMsg = this._prettyPrintWorker.postMessage; - this._prettyPrintWorker.postMessage = data => { - dumpn("Sending message to prettyPrintWorker: " - + JSON.stringify(data, null, 2) + "\n"); - return postMsg.call(this._prettyPrintWorker, data); - }; - } + this._prettyPrintWorker = new DevToolsWorker( + "resource://gre/modules/devtools/server/actors/pretty-print-worker.js", + { name: "pretty-print", + verbose: dumpn.wantLogging } + ); } return this._prettyPrintWorker; }, - _onPrettyPrintError: function ({ message, filename, lineno }) { - reportError(new Error(message + " @ " + filename + ":" + lineno)); - }, - - _onPrettyPrintMsg: function ({ data }) { - dumpn("Received message from prettyPrintWorker: " - + JSON.stringify(data, null, 2) + "\n"); - }, - /** * Keep track of all of the nested event loops we use to pause the debuggee * when we hit a breakpoint/debugger statement/etc in one place so we can @@ -515,7 +582,7 @@ ThreadActor.prototype = { * them in a stack) because we can pause within client evals. */ _threadPauseEventLoops: null, - _pushThreadPause: function TA__pushThreadPause() { + _pushThreadPause: function () { if (!this._threadPauseEventLoops) { this._threadPauseEventLoops = []; } @@ -523,163 +590,101 @@ ThreadActor.prototype = { this._threadPauseEventLoops.push(eventLoop); eventLoop.enter(); }, - _popThreadPause: function TA__popThreadPause() { + _popThreadPause: function () { const eventLoop = this._threadPauseEventLoops.pop(); dbg_assert(eventLoop, "Should have an event loop."); eventLoop.resolve(); }, - clearDebuggees: function TA_clearDebuggees() { - if (this.dbg) { + /** + * Remove all debuggees and clear out the thread's sources. + */ + clearDebuggees: function () { + if (this._dbg) { this.dbg.removeAllDebuggees(); } - this.conn.removeActorPool(this._threadLifetimePool || undefined); - this._threadLifetimePool = null; this._sources = null; + this._scripts = null; }, /** - * Add a debuggee global to the Debugger object. - * - * @returns the Debugger.Object that corresponds to the global. + * Listener for our |Debugger|'s "newGlobal" event. */ - addDebuggee: function TA_addDebuggee(aGlobal) { - let globalDebugObject; - try { - globalDebugObject = this.dbg.addDebuggee(aGlobal); - } catch (e) { - // Ignore attempts to add the debugger's compartment as a debuggee. - dumpn("Ignoring request to add the debugger's compartment as a debuggee"); - } - return globalDebugObject; + onNewGlobal: function (aGlobal) { + // Notify the client. + this.conn.send({ + from: this.actorID, + type: "newGlobal", + // TODO: after bug 801084 lands see if we need to JSONify this. + hostAnnotations: aGlobal.hostAnnotations + }); }, - /** - * Initialize the Debugger. - */ - _initDebugger: function TA__initDebugger() { - this.dbg = new Debugger(); - this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this); - this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this); - this.dbg.onNewScript = this.onNewScript.bind(this); - this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this); - // Keep the debugger disabled until a client attaches. - this.dbg.enabled = this._state != "detached"; - }, - - /** - * Remove a debuggee global from the JSInspector. - */ - removeDebugee: function TA_removeDebuggee(aGlobal) { - try { - this.dbg.removeDebuggee(aGlobal); - } catch(ex) { - // XXX: This debuggee has code currently executing on the stack, - // we need to save this for later. - } - }, - - /** - * Add the provided window and all windows in its frame tree as debuggees. - * - * @returns the Debugger.Object that corresponds to the window. - */ - _addDebuggees: function TA__addDebuggees(aWindow) { - let globalDebugObject = this.addDebuggee(aWindow); - let frames = aWindow.frames; - if (frames) { - for (let i = 0; i < frames.length; i++) { - this._addDebuggees(frames[i]); - } - } - return globalDebugObject; - }, - - /** - * An object that will be used by ThreadActors to tailor their behavior - * depending on the debugging context being required (chrome or content). - */ - globalManager: { - findGlobals: function TA_findGlobals() { - this.globalDebugObject = this._addDebuggees(this.global); - }, - - /** - * A function that the engine calls when a new global object has been - * created. - * - * @param aGlobal Debugger.Object - * The new global object that was created. - */ - onNewGlobal: function TA_onNewGlobal(aGlobal) { - // Content debugging only cares about new globals in the contant window, - // like iframe children. - if (aGlobal.hostAnnotations && - aGlobal.hostAnnotations.type == "document" && - aGlobal.hostAnnotations.element === this.global) { - this.addDebuggee(aGlobal); - // Notify the client. - this.conn.send({ - from: this.actorID, - type: "newGlobal", - // TODO: after bug 801084 lands see if we need to JSONify this. - hostAnnotations: aGlobal.hostAnnotations - }); - } - } - }, - - disconnect: function TA_disconnect() { + disconnect: function () { dumpn("in ThreadActor.prototype.disconnect"); if (this._state == "paused") { this.onResume(); } - this._state = "exited"; + // Blow away our source actor ID store because those IDs are only + // valid for this connection. This is ok because we never keep + // things like breakpoints across connections. + this._sourceActorStore = null; + // events.off(this._parent, "window-ready", this._onWindowReady); this.clearDebuggees(); + this.conn.removeActorPool(this._threadLifetimePool); + this._threadLifetimePool = null; if (this._prettyPrintWorker) { - this._prettyPrintWorker.removeEventListener( - "error", this._onPrettyPrintError, false); - this._prettyPrintWorker.removeEventListener( - "message", this._onPrettyPrintMsg, false); - this._prettyPrintWorker.terminate(); + this._prettyPrintWorker.destroy(); this._prettyPrintWorker = null; } - if (!this.dbg) { + if (!this._dbg) { return; } - this.dbg.enabled = false; - this.dbg = null; + this._dbg.enabled = false; + this._dbg = null; }, /** * Disconnect the debugger and put the actor in the exited state. */ - exit: function TA_exit() { + exit: function () { this.disconnect(); + this._state = "exited"; }, // Request handlers - onAttach: function TA_onAttach(aRequest) { + onAttach: function (aRequest) { if (this.state === "exited") { return { type: "exited" }; } if (this.state !== "detached") { - return { error: "wrongState" }; + return { error: "wrongState", + message: "Current state is " + this.state }; } this._state = "attached"; + this._debuggerSourcesSeen = new Set(); update(this._options, aRequest.options || {}); + this.sources.reconfigure(this._options); + this.sources.on('newSource', (name, source) => { + this.onNewSource(source); + }); - if (!this.dbg) { - this._initDebugger(); - } - this.findGlobals(); + // Initialize an event loop stack. This can't be done in the constructor, + // because this.conn is not yet initialized by the actor pool at that time. + this._nestedEventLoops = new EventLoopStack({ + hooks: this._parent, + connection: this.conn, + thread: this + }); + + // this.dbg.addDebuggees(); this.dbg.enabled = true; try { // Put ourselves in the paused state. @@ -708,22 +713,25 @@ ThreadActor.prototype = { } }, - onDetach: function TA_onDetach(aRequest) { + onDetach: function (aRequest) { this.disconnect(); - dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet"); + this._state = "detached"; + this._debuggerSourcesSeen = null; + + DevToolsUtils.dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet"); return { type: "detached" }; }, - onReconfigure: function TA_onReconfigure(aRequest) { + onReconfigure: function (aRequest) { if (this.state == "exited") { return { error: "wrongState" }; } update(this._options, aRequest.options || {}); - // Clear existing sources, so they can be recreated on next access. - this._sources = null; + // Update the global source store + this.sources.reconfigure(this._options); return {}; }, @@ -740,8 +748,7 @@ ThreadActor.prototype = { * Hook to modify the packet before it is sent. Feel free to return a * promise. */ - _pauseAndRespond: function TA__pauseAndRespond(aFrame, aReason, - onPacket=function (k) { return k; }) { + _pauseAndRespond: function (aFrame, aReason, onPacket=function (k) { return k; }) { try { let packet = this._paused(aFrame); if (!packet) { @@ -749,8 +756,28 @@ ThreadActor.prototype = { } packet.why = aReason; - this.sources.getOriginalLocation(packet.frame.where).then(aOrigPosition => { - packet.frame.where = aOrigPosition; + let generatedLocation = this.sources.getFrameLocation(aFrame); + this.sources.getOriginalLocation(generatedLocation) + .then((originalLocation) => { + if (!originalLocation.originalSourceActor) { + // The only time the source actor will be null is if there + // was a sourcemap and it tried to look up the original + // location but there was no original URL. This is a strange + // scenario so we simply don't pause. + DevToolsUtils.reportException( + 'ThreadActor', + new Error('Attempted to pause in a script with a sourcemap but ' + + 'could not find original location.') + ); + + return undefined; + } + + packet.frame.where = { + source: originalLocation.originalSourceActor.form(), + line: originalLocation.originalLine, + column: originalLocation.originalColumn + }; resolve(onPacket(packet)) .then(null, error => { reportError(error); @@ -769,7 +796,10 @@ ThreadActor.prototype = { reportError(e, "Got an exception during TA__pauseAndRespond: "); } - return undefined; + // If the browser tab has been closed, terminate the debuggee script + // instead of continuing. Executing JS after the content window is gone is + // a bad idea. + return this._tabClosed ? null : undefined; }, /** @@ -779,7 +809,7 @@ ThreadActor.prototype = { * The request packet received over the RDP. * @returns A response packet. */ - _forceCompletion: function TA__forceCompletion(aRequest) { + _forceCompletion: function (aRequest) { // TODO: remove this when Debugger.Frame.prototype.pop is implemented in // bug 736733. return { @@ -788,11 +818,12 @@ ThreadActor.prototype = { }; }, - _makeOnEnterFrame: function TA__makeOnEnterFrame({ pauseAndRespond }) { + _makeOnEnterFrame: function ({ pauseAndRespond }) { return aFrame => { - const generatedLocation = getFrameLocation(aFrame); - let { url } = this.synchronize(this.sources.getOriginalLocation( + const generatedLocation = this.sources.getFrameLocation(aFrame); + let { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation( generatedLocation)); + let url = originalSourceActor.url; return this.sources.isBlackBoxed(url) ? undefined @@ -800,13 +831,14 @@ ThreadActor.prototype = { }; }, - _makeOnPop: function TA__makeOnPop({ thread, pauseAndRespond, createValueGrip }) { + _makeOnPop: function ({ thread, pauseAndRespond, createValueGrip }) { return function (aCompletion) { // onPop is called with 'this' set to the current frame. - const generatedLocation = getFrameLocation(this); - const { url } = thread.synchronize(thread.sources.getOriginalLocation( + const generatedLocation = thread.sources.getFrameLocation(this); + const { originalSourceActor } = thread.unsafeSynchronize(thread.sources.getOriginalLocation( generatedLocation)); + const url = originalSourceActor.url; if (thread.sources.isBlackBoxed(url)) { return undefined; @@ -832,13 +864,21 @@ ThreadActor.prototype = { }; }, - _makeOnStep: function TA__makeOnStep({ thread, pauseAndRespond, startFrame, - startLocation }) { + _makeOnStep: function ({ thread, pauseAndRespond, startFrame, + startLocation, steppingType }) { + // Breaking in place: we should always pause. + if (steppingType === "break") { + return function () { + return pauseAndRespond(this); + }; + } + + // Otherwise take what a "step" means into consideration. return function () { // onStep is called with 'this' set to the current frame. - const generatedLocation = getFrameLocation(this); - const newLocation = thread.synchronize(thread.sources.getOriginalLocation( + const generatedLocation = thread.sources.getFrameLocation(this); + const newLocation = thread.unsafeSynchronize(thread.sources.getOriginalLocation( generatedLocation)); // Cases when we should pause because we have executed enough to consider @@ -857,15 +897,15 @@ ThreadActor.prototype = { // 2.2. The source we are in is black boxed. // Cases 2.1 and 2.2 - if (newLocation.url == null - || thread.sources.isBlackBoxed(newLocation.url)) { + if (newLocation.originalUrl == null + || thread.sources.isBlackBoxed(newLocation.originalUrl)) { return undefined; } // Cases 1.1, 1.2 and 1.3 if (this !== startFrame - || startLocation.url !== newLocation.url - || startLocation.line !== newLocation.line) { + || startLocation.originalUrl !== newLocation.originalUrl + || startLocation.originalLine !== newLocation.originalLine) { return pauseAndRespond(this); } @@ -878,19 +918,21 @@ ThreadActor.prototype = { /** * Define the JS hook functions for stepping. */ - _makeSteppingHooks: function TA__makeSteppingHooks(aStartLocation) { + _makeSteppingHooks: function (aStartLocation, steppingType) { // Bind these methods and state because some of the hooks are called // with 'this' set to the current frame. Rather than repeating the // binding in each _makeOnX method, just do it once here and pass it // in to each function. const steppingHookState = { - pauseAndRespond: (aFrame, onPacket=(k)=>k) => { - this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket); + pauseAndRespond: (aFrame, onPacket=k=>k) => { + return this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket); }, - createValueGrip: this.createValueGrip.bind(this), + createValueGrip: v => createValueGrip(v, this._pausePool, + this.objectGrip), thread: this, startFrame: this.youngestFrame, - startLocation: aStartLocation + startLocation: aStartLocation, + steppingType: steppingType }; return { @@ -909,17 +951,18 @@ ThreadActor.prototype = { * @returns A promise that resolves to true once the hooks are attached, or is * rejected with an error packet. */ - _handleResumeLimit: function TA__handleResumeLimit(aRequest) { + _handleResumeLimit: function (aRequest) { let steppingType = aRequest.resumeLimit.type; - if (["step", "next", "finish"].indexOf(steppingType) == -1) { + if (["break", "step", "next", "finish"].indexOf(steppingType) == -1) { return reject({ error: "badParameterType", message: "Unknown resumeLimit type" }); } - const generatedLocation = getFrameLocation(this.youngestFrame); + const generatedLocation = this.sources.getFrameLocation(this.youngestFrame); return this.sources.getOriginalLocation(generatedLocation) .then(originalLocation => { - const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation); + const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation, + steppingType); // Make sure there is still a frame on the stack if we are to continue // stepping. @@ -929,6 +972,7 @@ ThreadActor.prototype = { case "step": this.dbg.onEnterFrame = onEnterFrame; // Fall through. + case "break": case "next": if (stepFrame.script) { stepFrame.onStep = onStep; @@ -951,11 +995,13 @@ ThreadActor.prototype = { * @param Debugger.Frame aFrame * The frame we want to clear the stepping hooks from. */ - _clearSteppingHooks: function TA__clearSteppingHooks(aFrame) { - while (aFrame) { - aFrame.onStep = undefined; - aFrame.onPop = undefined; - aFrame = aFrame.older; + _clearSteppingHooks: function (aFrame) { + if (aFrame && aFrame.live) { + while (aFrame) { + aFrame.onStep = undefined; + aFrame.onPop = undefined; + aFrame = aFrame.older; + } } }, @@ -965,7 +1011,7 @@ ThreadActor.prototype = { * @param Object aRequest * The resume request packet received over the RDP. */ - _maybeListenToEvents: function TA__maybeListenToEvents(aRequest) { + _maybeListenToEvents: function (aRequest) { // Break-on-DOMEvents is only supported in content debugging. let events = aRequest.pauseOnDOMEvents; if (this.global && events && @@ -978,10 +1024,20 @@ ThreadActor.prototype = { } }, + /** + * If we are tasked with breaking on the load event, we have to add the + * listener early enough. + */ + _onWindowReady: function () { + this._maybeListenToEvents({ + pauseOnDOMEvents: this._pauseOnDOMEvents + }); + }, + /** * Handle a protocol request to resume execution of the debuggee. */ - onResume: function TA_onResume(aRequest) { + onResume: function (aRequest) { if (this._state !== "paused") { return { error: "wrongState", @@ -993,15 +1049,15 @@ ThreadActor.prototype = { // In case of multiple nested event loops (due to multiple debuggers open in // different tabs or multiple debugger clients connected to the same tab) // only allow resumption in a LIFO order. - // James commented - // if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl - // && this._nestedEventLoops.lastPausedUrl !== this._hooks.url) { - // return { - // error: "wrongOrder", - // message: "trying to resume in the wrong order.", - // lastPausedUrl: this._nestedEventLoops.lastPausedUrl - // }; - // } + if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl + && (this._nestedEventLoops.lastPausedUrl !== this._parent.url + || this._nestedEventLoops.lastConnection !== this.conn)) { + return { + error: "wrongOrder", + message: "trying to resume in the wrong order.", + lastPausedUrl: this._nestedEventLoops.lastPausedUrl + }; + } if (aRequest && aRequest.forceCompletion) { return this._forceCompletion(aRequest); @@ -1025,11 +1081,16 @@ ThreadActor.prototype = { let packet = this._resumed(); this._popThreadPause(); + // Tell anyone who cares of the resume (as of now, that's the xpcshell + // harness) + // if (Services.obs) { + // Services.obs.notifyObservers(this, "devtools-thread-resumed", null); + // } return packet; }, error => { return error instanceof Error ? { error: "unknownError", - message: safeErrorString(error) } + message: DevToolsUtils.safeErrorString(error) } // It is a known error, and the promise was rejected with an error // packet. : error; @@ -1039,11 +1100,15 @@ ThreadActor.prototype = { /** * Spin up a nested event loop so we can synchronously resolve a promise. * + * DON'T USE THIS UNLESS YOU ABSOLUTELY MUST! Nested event loops suck: the + * world's state can change out from underneath your feet because JS is no + * longer run-to-completion. + * * @param aPromise * The promise we want to resolve. * @returns The promise's resolution. */ - synchronize: function(aPromise) { + unsafeSynchronize: function(aPromise) { let needNest = true; let eventLoop; let returnVal; @@ -1054,7 +1119,7 @@ ThreadActor.prototype = { returnVal = aResolvedVal; }) .then(null, (aError) => { - reportError(aError, "Error inside synchronize:"); + reportError(aError, "Error inside unsafeSynchronize:"); }) .then(() => { if (eventLoop) { @@ -1126,7 +1191,29 @@ ThreadActor.prototype = { let l = Object.create(null); l.type = handler.type; let listener = handler.listenerObject; - l.script = this.globalDebugObject.makeDebuggeeValue(listener).script; + let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener); + // If the listener is an object with a 'handleEvent' method, use that. + if (listenerDO.class == "Object" || listenerDO.class == "XULElement") { + // For some events we don't have permission to access the + // 'handleEvent' property when running in content scope. + if (!listenerDO.unwrap()) { + continue; + } + let heDesc; + while (!heDesc && listenerDO) { + heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent"); + listenerDO = listenerDO.proto; + } + if (heDesc && heDesc.value) { + listenerDO = heDesc.value; + } + } + // When the listener is a bound function, we are actually interested in + // the target function. + while (listenerDO.isBoundFunction) { + listenerDO = listenerDO.boundTargetFunction; + } + l.script = listenerDO.script; // Chrome listeners won't be converted to debuggee values, since their // compartment is not added as a debuggee. if (!l.script) @@ -1138,24 +1225,21 @@ ThreadActor.prototype = { }, /** - * Set a breakpoint on the first bytecode offset in the provided script. + * Set a breakpoint on the first line of the given script that has an entry + * point. */ _breakOnEnter: function(script) { let offsets = script.getAllOffsets(); for (let line = 0, n = offsets.length; line < n; line++) { if (offsets[line]) { - let location = { url: script.url, line: line }; - let resp = this._createAndStoreBreakpoint(location); - dbg_assert(!resp.actualLocation, "No actualLocation should be returned"); - if (resp.error) { - reportError(new Error("Unable to set breakpoint on event listener")); - return; - } - let bp = this.breakpointStore.getBreakpoint(location); - let bpActor = bp.actor; - dbg_assert(bp, "Breakpoint must exist"); - dbg_assert(bpActor, "Breakpoint actor must be created"); - this._hiddenBreakpoints.set(bpActor.actorID, bpActor); + // N.B. Hidden breakpoints do not have an original location, and are not + // stored in the breakpoint actor map. + let actor = new BreakpointActor(this); + this.threadLifetimePool.addActor(actor); + let scripts = this.scripts.getScriptsBySourceAndLine(script.source, line); + let entryPoints = findEntryPointsForLine(scripts, line); + setBreakpointAtEntryPoints(actor, entryPoints); + this._hiddenBreakpoints.set(actor.actorID, actor); break; } } @@ -1164,7 +1248,7 @@ ThreadActor.prototype = { /** * Helper method that returns the next frame when stepping. */ - _getNextStepFrame: function TA__getNextStepFrame(aFrame) { + _getNextStepFrame: function (aFrame) { let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame; if (!stepFrame || !stepFrame.script) { stepFrame = null; @@ -1172,7 +1256,7 @@ ThreadActor.prototype = { return stepFrame; }, - onClientEvaluate: function TA_onClientEvaluate(aRequest) { + onClientEvaluate: function (aRequest) { if (this.state !== "paused") { return { error: "wrongState", message: "Debuggee must be paused to evaluate code." }; @@ -1208,7 +1292,7 @@ ThreadActor.prototype = { return packet; }, - onFrames: function TA_onFrames(aRequest) { + onFrames: function (aRequest) { if (this.state !== "paused") { return { error: "wrongState", message: "Stack frames are only available while the debuggee is paused."}; @@ -1234,14 +1318,19 @@ ThreadActor.prototype = { form.depth = i; frames.push(form); - let promise = this.sources.getOriginalLocation(form.where) - .then((aOrigLocation) => { - form.where = aOrigLocation; - let source = this.sources.source({ url: form.where.url }); - if (source) { - form.source = source.form(); - } - }); + let promise = this.sources.getOriginalLocation(new GeneratedLocation( + this.sources.createNonSourceMappedActor(frame.script.source), + form.where.line, + form.where.column + )).then((originalLocation) => { + let sourceForm = originalLocation.originalSourceActor.form(); + form.where = { + source: sourceForm, + line: originalLocation.originalLine, + column: originalLocation.originalColumn + }; + form.source = sourceForm; + }); promises.push(promise); } @@ -1250,14 +1339,14 @@ ThreadActor.prototype = { }); }, - onReleaseMany: function TA_onReleaseMany(aRequest) { + onReleaseMany: function (aRequest) { if (!aRequest.actors) { return { error: "missingParameter", message: "no actors were specified" }; } let res; - for each (let actorID in aRequest.actors) { + for (let actorID of aRequest.actors) { let actor = this.threadLifetimePool.get(actorID); if (!actor) { if (!res) { @@ -1271,320 +1360,52 @@ ThreadActor.prototype = { return res ? res : {}; }, - /** - * Handle a protocol request to set a breakpoint. - */ - onSetBreakpoint: function TA_onSetBreakpoint(aRequest) { - if (this.state !== "paused") { - return { error: "wrongState", - message: "Breakpoints can only be set while the debuggee is paused."}; - } - - let { url: originalSource, - line: originalLine, - column: originalColumn } = aRequest.location; - - let locationPromise = this.sources.getGeneratedLocation(aRequest.location); - return locationPromise.then(({url, line, column}) => { - if (line == null || - line < 0 || - this.dbg.findScripts({ url: url }).length == 0) { - return { error: "noScript" }; - } - - let response = this._createAndStoreBreakpoint({ - url: url, - line: line, - column: column - }); - // If the original location of our generated location is different from - // the original location we attempted to set the breakpoint on, we will - // need to know so that we can set actualLocation on the response. - let originalLocation = this.sources.getOriginalLocation({ - url: url, - line: line, - column: column - }); - - return all([response, originalLocation]) - .then(([aResponse, {url, line}]) => { - if (aResponse.actualLocation) { - let actualOrigLocation = this.sources.getOriginalLocation(aResponse.actualLocation); - return actualOrigLocation.then(({ url, line, column }) => { - if (url !== originalSource - || line !== originalLine - || column !== originalColumn) { - aResponse.actualLocation = { - url: url, - line: line, - column: column - }; - } - return aResponse; - }); - } - - if (url !== originalSource || line !== originalLine) { - aResponse.actualLocation = { url: url, line: line }; - } - - return aResponse; - }); - }); - }, - - /** - * Create a breakpoint at the specified location and store it in the - * cache. Takes ownership of `aLocation`. - * - * @param Object aLocation - * An object of the form { url, line[, column] } - */ - _createAndStoreBreakpoint: function (aLocation) { - // Add the breakpoint to the store for later reuse, in case it belongs to a - // script that hasn't appeared yet. - this.breakpointStore.addBreakpoint(aLocation); - return this._setBreakpoint(aLocation); - }, - - /** - * Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint - * is being set contains no code, then the breakpoint will slide down to the - * next line that has runnable code. In this case the server breakpoint cache - * will be updated, so callers that iterate over the breakpoint cache should - * take that into account. - * - * @param object aLocation - * The location of the breakpoint (in the generated source, if source - * mapping). - */ - _setBreakpoint: function TA__setBreakpoint(aLocation) { - let actor; - let storedBp = this.breakpointStore.getBreakpoint(aLocation); - if (storedBp.actor) { - actor = storedBp.actor; - } else { - storedBp.actor = actor = new BreakpointActor(this, { - url: aLocation.url, - line: aLocation.line, - column: aLocation.column - }); - this._hooks.addToParentPool(actor); - } - - // Find all scripts matching the given location - let scripts = this.dbg.findScripts(aLocation); - if (scripts.length == 0) { - return { - error: "noScript", - actor: actor.actorID - }; - } - - /** - * For each script, if the given line has at least one entry point, set a - * breakpoint on the bytecode offets for each of them. - */ - - // Debugger.Script -> array of offset mappings - let scriptsAndOffsetMappings = new Map(); - - for (let script of scripts) { - this._findClosestOffsetMappings(aLocation, - script, - scriptsAndOffsetMappings); - } - - if (scriptsAndOffsetMappings.size > 0) { - for (let [script, mappings] of scriptsAndOffsetMappings) { - for (let offsetMapping of mappings) { - script.setBreakpoint(offsetMapping.offset, actor); - } - actor.addScript(script, this); - } - - return { - actor: actor.actorID - }; - } - - /** - * If we get here, no breakpoint was set. This is because the given line - * has no entry points, for example because it is empty. As a fallback - * strategy, we try to set the breakpoint on the smallest line greater - * than or equal to the given line that as at least one entry point. - */ - - // Find all innermost scripts matching the given location - let scripts = this.dbg.findScripts({ - url: aLocation.url, - line: aLocation.line, - innermost: true - }); - - /** - * For each innermost script, look for the smallest line greater than or - * equal to the given line that has one or more entry points. If found, set - * a breakpoint on the bytecode offset for each of its entry points. - */ - let actualLocation; - let found = false; - for (let script of scripts) { - let offsets = script.getAllOffsets(); - for (let line = aLocation.line; line < offsets.length; ++line) { - if (offsets[line]) { - for (let offset of offsets[line]) { - script.setBreakpoint(offset, actor); - } - actor.addScript(script, this); - if (!actualLocation) { - actualLocation = { - url: aLocation.url, - line: line, - column: 0 - }; - } - found = true; - break; - } - } - } - if (found) { - let existingBp = this.breakpointStore.hasBreakpoint(actualLocation); - - if (existingBp && existingBp.actor) { - /** - * We already have a breakpoint actor for the actual location, so - * actor we created earlier is now redundant. Delete it, update the - * breakpoint store, and return the actor for the actual location. - */ - actor.onDelete(); - this.breakpointStore.removeBreakpoint(aLocation); - return { - actor: existingBp.actor.actorID, - actualLocation: actualLocation - }; - } else { - /** - * We don't have a breakpoint actor for the actual location yet. - * Instead or creating a new actor, reuse the actor we created earlier, - * and update the breakpoint store. - */ - actor.location = actualLocation; - this.breakpointStore.addBreakpoint({ - actor: actor, - url: actualLocation.url, - line: actualLocation.line, - column: actualLocation.column - }); - this.breakpointStore.removeBreakpoint(aLocation); - return { - actor: actor.actorID, - actualLocation: actualLocation - }; - } - } - - /** - * If we get here, no line matching the given line was found, so just - * fail epically. - */ - return { - error: "noCodeAtLineColumn", - actor: actor.actorID - }; - }, - - /** - * Find all of the offset mappings associated with `aScript` that are closest - * to `aTargetLocation`. If new offset mappings are found that are closer to - * `aTargetOffset` than the existing offset mappings inside - * `aScriptsAndOffsetMappings`, we empty that map and only consider the - * closest offset mappings. If there is no column in `aTargetLocation`, we add - * all offset mappings that are on the given line. - * - * @param Object aTargetLocation - * An object of the form { url, line[, column] }. - * @param Debugger.Script aScript - * The script in which we are searching for offsets. - * @param Map aScriptsAndOffsetMappings - * A Map object which maps Debugger.Script instances to arrays of - * offset mappings. This is an out param. - */ - _findClosestOffsetMappings: function TA__findClosestOffsetMappings(aTargetLocation, - aScript, - aScriptsAndOffsetMappings) { - // If we are given a column, we will try and break only at that location, - // otherwise we will break anytime we get on that line. - - if (aTargetLocation.column == null) { - let offsetMappings = aScript.getLineOffsets(aTargetLocation.line) - .map(o => ({ - line: aTargetLocation.line, - offset: o - })); - if (offsetMappings.length) { - aScriptsAndOffsetMappings.set(aScript, offsetMappings); - } - return; - } - - let offsetMappings = aScript.getAllColumnOffsets() - .filter(({ lineNumber }) => lineNumber === aTargetLocation.line); - - // Attempt to find the current closest offset distance from the target - // location by grabbing any offset mapping in the map by doing one iteration - // and then breaking (they all have the same distance from the target - // location). - let closestDistance = Infinity; - if (aScriptsAndOffsetMappings.size) { - for (let mappings of aScriptsAndOffsetMappings.values()) { - closestDistance = Math.abs(aTargetLocation.column - mappings[0].columnNumber); - break; - } - } - - for (let mapping of offsetMappings) { - let currentDistance = Math.abs(aTargetLocation.column - mapping.columnNumber); - - if (currentDistance > closestDistance) { - continue; - } else if (currentDistance < closestDistance) { - closestDistance = currentDistance; - aScriptsAndOffsetMappings.clear(); - aScriptsAndOffsetMappings.set(aScript, [mapping]); - } else { - if (!aScriptsAndOffsetMappings.has(aScript)) { - aScriptsAndOffsetMappings.set(aScript, []); - } - aScriptsAndOffsetMappings.get(aScript).push(mapping); - } - } - }, - /** * Get the script and source lists from the debugger. - * - * TODO bug 637572: we should be dealing with sources directly, not inferring - * them through scripts. */ - _discoverSources: function TA__discoverSources() { - // Only get one script per url. - let scriptsByUrl = {}; - for (let s of this.dbg.findScripts()) { - scriptsByUrl[s.url] = s; + _discoverSources: function () { + // Only get one script per Debugger.Source. + const sourcesToScripts = new Map(); + const scripts = this.scripts.getAllScripts(); + for (let i = 0, len = scripts.length; i < len; i++) { + let s = scripts[i]; + if (s.source && s.source.url) { + sourcesToScripts.set(s.source, s); + } } - return all([this.sources.sourcesForScript(scriptsByUrl[s]) - for (s of Object.keys(scriptsByUrl))]); + // let arr = []; + // for (let [key, script] of sourcesToScripts) { + // arr.push(this.sources.createSourceActors(script.source)); + // } + // return all(arr); + return all([...sourcesToScripts.values()].map(script => { + return this.sources.createSourceActors(script.source); + })); }, - onSources: function TA_onSources(aRequest) { + onSources: function (aRequest) { + // let promise = this._discoverSources(); return this._discoverSources().then(() => { + // No need to flush the new source packets here, as we are sending the + // list of sources out immediately and we don't need to invoke the + // overhead of an RDP packet for every source right now. Let the default + // timeout flush the buffered packets. + return { - sources: [s.form() for (s of this.sources.iter())] + sources: this.sources.iter().map(s => s.form()) }; }); + // return this._discoverSources().then(() => { + // // No need to flush the new source packets here, as we are sending the + // // list of sources out immediately and we don't need to invoke the + // // overhead of an RDP packet for every source right now. Let the default + // // timeout flush the buffered packets. + + // return { + // sources: this.sources.iter().map(s => s.form()) + // }; + // }); }, /** @@ -1595,17 +1416,16 @@ ThreadActor.prototype = { * caches won't hold on to the Debugger.Script objects leaking memory. */ disableAllBreakpoints: function () { - for (let bp of this.breakpointStore.findBreakpoints()) { - if (bp.actor) { - bp.actor.removeScripts(); - } + for (let bpActor of this.breakpointActorMap.findActors()) { + bpActor.removeScripts(); } }, + /** * Handle a protocol request to pause the debuggee. */ - onInterrupt: function TA_onInterrupt(aRequest) { + onInterrupt: function (aRequest) { if (this.state == "exited") { return { type: "exited" }; } else if (this.state == "paused") { @@ -1618,7 +1438,19 @@ ThreadActor.prototype = { } try { - // Put ourselves in the paused state. + // If execution should pause just before the next JavaScript bytecode is + // executed, just set an onEnterFrame handler. + if (aRequest.when == "onNext") { + let onEnterFrame = (aFrame) => { + return this._pauseAndRespond(aFrame, { type: "interrupted", onNext: true }); + }; + this.dbg.onEnterFrame = onEnterFrame; + + return { type: "willInterrupt" }; + } + + // If execution should pause immediately, just put ourselves in the paused + // state. let packet = this._paused(); if (!packet) { return { error: "notInterrupted" }; @@ -1645,7 +1477,7 @@ ThreadActor.prototype = { /** * Handle a protocol request to retrieve all the event listeners on the page. */ - onEventListeners: function TA_onEventListeners(aRequest) { + onEventListeners: function (aRequest) { // This request is only supported in content debugging. if (!this.global) { return { @@ -1668,27 +1500,56 @@ ThreadActor.prototype = { // Create a form object for serializing the listener via the protocol. let listenerForm = Object.create(null); let listener = handler.listenerObject; - // Native event listeners don't provide any listenerObject and are not - // that useful to a JS debugger. - if (!listener) { + // Native event listeners don't provide any listenerObject or type and + // are not that useful to a JS debugger. + if (!listener || !handler.type) { continue; } // There will be no tagName if the event listener is set on the window. - let selector = node.tagName ? findCssSelector(node) : "window"; + let selector = node.tagName ? CssLogic.findCssSelector(node) : "window"; let nodeDO = this.globalDebugObject.makeDebuggeeValue(node); listenerForm.node = { selector: selector, - object: this.createValueGrip(nodeDO) + object: createValueGrip(nodeDO, this._pausePool, this.objectGrip) }; listenerForm.type = handler.type; listenerForm.capturing = handler.capturing; listenerForm.allowsUntrusted = handler.allowsUntrusted; listenerForm.inSystemEventGroup = handler.inSystemEventGroup; - listenerForm.isEventHandler = !!node["on" + listenerForm.type]; + let handlerName = "on" + listenerForm.type; + listenerForm.isEventHandler = false; + if (typeof node.hasAttribute !== "undefined") { + listenerForm.isEventHandler = !!node.hasAttribute(handlerName); + } + if (!!node[handlerName]) { + listenerForm.isEventHandler = !!node[handlerName]; + } // Get the Debugger.Object for the listener object. let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener); - listenerForm.function = this.createValueGrip(listenerDO); + // If the listener is an object with a 'handleEvent' method, use that. + if (listenerDO.class == "Object" || listenerDO.class == "XULElement") { + // For some events we don't have permission to access the + // 'handleEvent' property when running in content scope. + if (!listenerDO.unwrap()) { + continue; + } + let heDesc; + while (!heDesc && listenerDO) { + heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent"); + listenerDO = listenerDO.proto; + } + if (heDesc && heDesc.value) { + listenerDO = heDesc.value; + } + } + // When the listener is a bound function, we are actually interested in + // the target function. + while (listenerDO.isBoundFunction) { + listenerDO = listenerDO.boundTargetFunction; + } + listenerForm.function = createValueGrip(listenerDO, this._pausePool, + this.objectGrip); listeners.push(listenerForm); } } @@ -1698,7 +1559,7 @@ ThreadActor.prototype = { /** * Return the Debug.Frame for a frame mentioned by the protocol. */ - _requestFrame: function TA_requestFrame(aFrameID) { + _requestFrame: function (aFrameID) { if (!aFrameID) { return this.youngestFrame; } @@ -1710,7 +1571,7 @@ ThreadActor.prototype = { return undefined; }, - _paused: function TA__paused(aFrame) { + _paused: function (aFrame) { // We don't handle nested pauses correctly. Don't try - if we're // paused, just continue running whatever code triggered the pause. // We don't want to actually have nested pauses (although we @@ -1729,18 +1590,19 @@ ThreadActor.prototype = { aFrame.onStep = undefined; aFrame.onPop = undefined; } + // Clear DOM event breakpoints. // XPCShell tests don't use actual DOM windows for globals and cause // removeListenerForAllEvents to throw. - if (this.global && !this.global.toString().contains("Sandbox")) { - // let els = Cc["@mozilla.org/eventlistenerservice;1"] - // .getService(Ci.nsIEventListenerService); - // els.removeListenerForAllEvents(this.global, this._allEventsListener, true); - for (let [,bp] of this._hiddenBreakpoints) { - bp.onDelete(); - } - this._hiddenBreakpoints.clear(); - } + // if (!isWorker && this.global && !this.global.toString().includes("Sandbox")) { + // let els = Cc["@mozilla.org/eventlistenerservice;1"] + // .getService(Ci.nsIEventListenerService); + // els.removeListenerForAllEvents(this.global, this._allEventsListener, true); + // for (let [,bp] of this._hiddenBreakpoints) { + // bp.onDelete(); + // } + // this._hiddenBreakpoints.clear(); + // } this._state = "paused"; @@ -1777,7 +1639,7 @@ ThreadActor.prototype = { return packet; }, - _resumed: function TA_resumed() { + _resumed: function () { this._state = "running"; // Drop the actors in the pause actor pool. @@ -1794,14 +1656,14 @@ ThreadActor.prototype = { * * @returns A list of actor IDs whose frames have been popped. */ - _updateFrames: function TA_updateFrames() { + _updateFrames: function () { let popped = []; // Create the actor pool that will hold the still-living frames. let framePool = new ActorPool(this.conn); let frameList = []; - for each (let frameActor in this._frameActors) { + for (let frameActor of this._frameActors) { if (frameActor.frame.live) { framePool.addActor(frameActor); frameList.push(frameActor); @@ -1823,7 +1685,7 @@ ThreadActor.prototype = { return popped; }, - _createFrameActor: function TA_createFrameActor(aFrame) { + _createFrameActor: function (aFrame) { if (aFrame.actor) { return aFrame.actor; } @@ -1846,8 +1708,7 @@ ThreadActor.prototype = { * @return The EnvironmentActor for aEnvironment or undefined for host * functions or functions scoped to a non-debuggee global. */ - createEnvironmentActor: - function TA_createEnvironmentActor(aEnvironment, aPool) { + createEnvironmentActor: function (aEnvironment, aPool) { if (!aEnvironment) { return undefined; } @@ -1863,62 +1724,23 @@ ThreadActor.prototype = { return actor; }, - /** - * Create a grip for the given debuggee value. If the value is an - * object, will create an actor with the given lifetime. - */ - createValueGrip: function TA_createValueGrip(aValue, aPool=false) { - if (!aPool) { - aPool = this._pausePool; - } - - switch (typeof aValue) { - case "boolean": - return aValue; - case "string": - if (this._stringIsLong(aValue)) { - return this.longStringGrip(aValue, aPool); - } - return aValue; - case "number": - if (aValue === Infinity) { - return { type: "Infinity" }; - } else if (aValue === -Infinity) { - return { type: "-Infinity" }; - } else if (Number.isNaN(aValue)) { - return { type: "NaN" }; - } else if (!aValue && 1 / aValue === -Infinity) { - return { type: "-0" }; - } - return aValue; - case "undefined": - return { type: "undefined" }; - case "object": - if (aValue === null) { - return { type: "null" }; - } - return this.objectGrip(aValue, aPool); - default: - dbg_assert(false, "Failed to provide a grip for: " + aValue); - return null; - } - }, - /** * Return a protocol completion value representing the given * Debugger-provided completion value. */ - createProtocolCompletionValue: - function TA_createProtocolCompletionValue(aCompletion) { + createProtocolCompletionValue: function (aCompletion) { let protoValue = {}; - if ("return" in aCompletion) { - protoValue.return = this.createValueGrip(aCompletion.return); - } else if ("yield" in aCompletion) { - protoValue.return = this.createValueGrip(aCompletion.yield); - } else if ("throw" in aCompletion) { - protoValue.throw = this.createValueGrip(aCompletion.throw); - } else { + if (aCompletion == null) { protoValue.terminated = true; + } else if ("return" in aCompletion) { + protoValue.return = createValueGrip(aCompletion.return, + this._pausePool, this.objectGrip); + } else if ("throw" in aCompletion) { + protoValue.throw = createValueGrip(aCompletion.throw, + this._pausePool, this.objectGrip); + } else { + protoValue.return = createValueGrip(aCompletion.yield, + this._pausePool, this.objectGrip); } return protoValue; }, @@ -1931,7 +1753,7 @@ ThreadActor.prototype = { * @param aPool ActorPool * The actor pool where the new object actor will be added. */ - objectGrip: function TA_objectGrip(aValue, aPool) { + objectGrip: function (aValue, aPool) { if (!aPool.objectActors) { aPool.objectActors = new WeakMap(); } @@ -1942,7 +1764,20 @@ ThreadActor.prototype = { return this.threadLifetimePool.objectActors.get(aValue).grip(); } - let actor = new PauseScopedObjectActor(aValue, this); + let actor = new PauseScopedObjectActor(aValue, { + getGripDepth: () => this._gripDepth, + incrementGripDepth: () => this._gripDepth++, + decrementGripDepth: () => this._gripDepth--, + createValueGrip: v => createValueGrip(v, this._pausePool, + this.pauseObjectGrip), + sources: () => this.sources, + createEnvironmentActor: (env, pool) => + this.createEnvironmentActor(env, pool), + promote: () => this.threadObjectGrip(actor), + isThreadLifetimePool: () => + actor.registeredPool !== this.threadLifetimePool, + getGlobalDebugObject: () => this.globalDebugObject + }); aPool.addActor(actor); aPool.objectActors.set(aValue, actor); return actor.grip(); @@ -1954,7 +1789,7 @@ ThreadActor.prototype = { * @param aValue Debugger.Object * The debuggee object value. */ - pauseObjectGrip: function TA_pauseObjectGrip(aValue) { + pauseObjectGrip: function (aValue) { if (!this._pausePool) { throw "Object grip requested while not paused."; } @@ -1968,7 +1803,7 @@ ThreadActor.prototype = { * @param aActor object * The object actor. */ - threadObjectGrip: function TA_threadObjectGrip(aActor) { + threadObjectGrip: function (aActor) { // We want to reuse the existing actor ID, so we just remove it from the // current pool's weak map and then let pool.addActor do the rest. aActor.registeredPool.objectActors.delete(aActor.obj); @@ -1983,7 +1818,7 @@ ThreadActor.prototype = { * @param aRequest object * The protocol request object. */ - onThreadGrips: function OA_onThreadGrips(aRequest) { + onThreadGrips: function (aRequest) { if (this.state != "paused") { return { error: "wrongState" }; } @@ -2002,37 +1837,14 @@ ThreadActor.prototype = { return {}; }, - /** - * Create a grip for the given string. - * - * @param aString String - * The string we are creating a grip for. - * @param aPool ActorPool - * The actor pool where the new actor will be added. - */ - longStringGrip: function TA_longStringGrip(aString, aPool) { - if (!aPool.longStringActors) { - aPool.longStringActors = {}; - } - - if (aPool.longStringActors.hasOwnProperty(aString)) { - return aPool.longStringActors[aString].grip(); - } - - let actor = new LongStringActor(aString, this); - aPool.addActor(actor); - aPool.longStringActors[aString] = actor; - return actor.grip(); - }, - /** * Create a long string grip that is scoped to a pause. * * @param aString String * The string we are creating a grip for. */ - pauseLongStringGrip: function TA_pauseLongStringGrip (aString) { - return this.longStringGrip(aString, this._pausePool); + pauseLongStringGrip: function (aString) { + return longStringGrip(aString, this._pausePool); }, /** @@ -2041,19 +1853,8 @@ ThreadActor.prototype = { * @param aString String * The string we are creating a grip for. */ - threadLongStringGrip: function TA_pauseLongStringGrip (aString) { - return this.longStringGrip(aString, this._threadLifetimePool); - }, - - /** - * Returns true if the string is long enough to use a LongStringActor instead - * of passing the value directly over the protocol. - * - * @param aString String - * The string we are checking the length of. - */ - _stringIsLong: function TA__stringIsLong(aString) { - return aString.length >= DebuggerServer.LONG_STRING_LENGTH; + threadLongStringGrip: function (aString) { + return longStringGrip(aString, this._threadLifetimePool); }, // JS Debugger API hooks. @@ -2066,8 +1867,8 @@ ThreadActor.prototype = { * @param aException exception * The exception that was thrown in the debugger code. */ - uncaughtExceptionHook: function TA_uncaughtExceptionHook(aException) { - dumpn("Got an exception: " + aException.message + "\n" + aException.stack); + uncaughtExceptionHook: function (aException) { + DevToolsUtils.dumpn("Got an exception: " + aException.message + "\n" + aException.stack); }, /** @@ -2077,12 +1878,13 @@ ThreadActor.prototype = { * @param aFrame Debugger.Frame * The stack frame that contained the debugger statement. */ - onDebuggerStatement: function TA_onDebuggerStatement(aFrame) { + onDebuggerStatement: function (aFrame) { // Don't pause if we are currently stepping (in or over) or the frame is // black-boxed. - const generatedLocation = getFrameLocation(aFrame); - const { url } = this.synchronize(this.sources.getOriginalLocation( + const generatedLocation = this.sources.getFrameLocation(aFrame); + const { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation( generatedLocation)); + const url = originalSourceActor ? originalSourceActor.url : null; return this.sources.isBlackBoxed(url) || aFrame.onStep ? undefined @@ -2098,7 +1900,7 @@ ThreadActor.prototype = { * @param aValue object * The exception that was thrown. */ - onExceptionUnwind: function TA_onExceptionUnwind(aFrame, aValue) { + onExceptionUnwind: function (aFrame, aValue) { let willBeCaught = false; for (let frame = aFrame; frame != null; frame = frame.older) { if (frame.script.isInCatchScope(frame.offset)) { @@ -2111,9 +1913,10 @@ ThreadActor.prototype = { return undefined; } - const generatedLocation = getFrameLocation(aFrame); - const { url } = this.synchronize(this.sources.getOriginalLocation( + const generatedLocation = this.sources.getFrameLocation(aFrame); + const { sourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation( generatedLocation)); + const url = sourceActor ? sourceActor.url : null; if (this.sources.isBlackBoxed(url)) { return undefined; @@ -2126,7 +1929,9 @@ ThreadActor.prototype = { } packet.why = { type: "exception", - exception: this.createValueGrip(aValue) }; + exception: createValueGrip(aValue, this._pausePool, + this.objectGrip) + }; this.conn.send(packet); this._pushThreadPause(); @@ -2146,12 +1951,11 @@ ThreadActor.prototype = { * @param aGlobal Debugger.Object * A Debugger.Object instance whose referent is the global object. */ - onNewScript: function TA_onNewScript(aScript, aGlobal) { - this._addScript(aScript); - this.sources.sourcesForScript(aScript); + onNewScript: function (aScript, aGlobal) { + this._addSource(aScript.source); }, - onNewSource: function TA_onNewSource(aSource) { + onNewSource: function (aSource) { this.conn.send({ from: this.actorID, type: "newSource", @@ -2160,63 +1964,78 @@ ThreadActor.prototype = { }, /** - * Check if scripts from the provided source URL are allowed to be stored in - * the cache. - * - * @param aSourceUrl String - * The url of the script's source that will be stored. - * @returns true, if the script can be added, false otherwise. + * Restore any pre-existing breakpoints to the sources that we have access to. */ - _allowSource: function TA__allowSource(aSourceUrl) { - // Ignore anything we don't have a URL for (eval scripts, for example). - if (!aSourceUrl) - return false; - // Ignore XBL bindings for content debugging. - if (aSourceUrl.indexOf("chrome://") == 0) { - return false; + _restoreBreakpoints: function () { + if (this.breakpointActorMap.size === 0) { + return; } - // Ignore about:* pages for content debugging. - if (aSourceUrl.indexOf("about:") == 0) { - return false; - } - return true; - }, - /** - * Restore any pre-existing breakpoints to the scripts that we have access to. - */ - _restoreBreakpoints: function TA__restoreBreakpoints() { - for (let s of this.dbg.findScripts()) { - this._addScript(s); + for (let s of this.scripts.getSources()) { + this._addSource(s); } }, /** - * Add the provided script to the server cache. + * Add the provided source to the server cache. * - * @param aScript Debugger.Script - * The source script that will be stored. - * @returns true, if the script was added; false otherwise. + * @param aSource Debugger.Source + * The source that will be stored. + * @returns true, if the source was added; false otherwise. */ - _addScript: function TA__addScript(aScript) { - if (!this._allowSource(aScript.url)) { + _addSource: function (aSource) { + if (!this.sources.allowSource(aSource) || this._debuggerSourcesSeen.has(aSource)) { return false; } + // The scripts must be added to the ScriptStore before restoring + // breakpoints. If we try to add them to the ScriptStore any later, we can + // accidentally set a breakpoint in a top level script as a "closest match" + // because we wouldn't have added the child scripts to the ScriptStore yet. + this.scripts.addScripts(this.dbg.findScripts({ source: aSource })); + + let sourceActor = this.sources.createNonSourceMappedActor(aSource); + + // Go ahead and establish the source actors for this script, which + // fetches sourcemaps if available and sends onNewSource + // notifications. + // + // We need to use unsafeSynchronize here because if the page is being reloaded, + // this call will replace the previous set of source actors for this source + // with a new one. If the source actors have not been replaced by the time + // we try to reset the breakpoints below, their location objects will still + // point to the old set of source actors, which point to different scripts. + this.unsafeSynchronize(this.sources.createSourceActors(aSource)); + // Set any stored breakpoints. + let promises = []; - let endLine = aScript.startLine + aScript.lineCount - 1; - for (let bp of this.breakpointStore.findBreakpoints({ url: aScript.url })) { - // Only consider breakpoints that are not already associated with - // scripts, and limit search to the line numbers contained in the new - // script. - if (!bp.actor.scripts.length - && bp.line >= aScript.startLine - && bp.line <= endLine) { - this._setBreakpoint(bp); + for (let _actor of this.breakpointActorMap.findActors()) { + // XXX bug 1142115: We do async work in here, so we need to create a fresh + // binding because for/of does not yet do that in SpiderMonkey. + let actor = _actor; + + if (actor.isPending) { + promises.push(actor.originalLocation.originalSourceActor._setBreakpoint(actor)); + } else { + promises.push(this.sources.getAllGeneratedLocations(actor.originalLocation) + .then((generatedLocations) => { + if (generatedLocations.length > 0 && + generatedLocations[0].generatedSourceActor.actorID === sourceActor.actorID) { + sourceActor._setBreakpointAtAllGeneratedLocations( + actor, + generatedLocations + ); + } + })); } } + if (promises.length > 0) { + this.unsafeSynchronize(promise.all(promises)); + } + + this._debuggerSourcesSeen.add(aSource); return true; }, @@ -2224,7 +2043,7 @@ ThreadActor.prototype = { /** * Get prototypes and properties of multiple objects. */ - onPrototypesAndProperties: function TA_onPrototypesAndProperties(aRequest) { + onPrototypesAndProperties: function (aRequest) { let result = {}; for (let actorID of aRequest.actors) { // This code assumes that there are no lazily loaded actors returned @@ -2247,7 +2066,6 @@ ThreadActor.prototype = { return { from: this.actorID, actors: result }; } - }; ThreadActor.prototype.requestTypes = { @@ -2260,12 +2078,12 @@ ThreadActor.prototype.requestTypes = { "interrupt": ThreadActor.prototype.onInterrupt, "eventListeners": ThreadActor.prototype.onEventListeners, "releaseMany": ThreadActor.prototype.onReleaseMany, - "setBreakpoint": ThreadActor.prototype.onSetBreakpoint, "sources": ThreadActor.prototype.onSources, "threadGrips": ThreadActor.prototype.onThreadGrips, "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties }; +exports.ThreadActor = ThreadActor; /** * Creates a PauseActor. @@ -2304,7 +2122,7 @@ function PauseScopedActor() * @param aMethod Function * The function we are decorating. */ -PauseScopedActor.withPaused = function PSA_withPaused(aMethod) { +PauseScopedActor.withPaused = function (aMethod) { return function () { if (this.isPaused()) { return aMethod.apply(this, arguments); @@ -2319,7 +2137,7 @@ PauseScopedActor.prototype = { /** * Returns true if we are in the paused state. */ - isPaused: function PSA_isPaused() { + isPaused: function () { // When there is not a ThreadActor available (like in the webconsole) we // have to be optimistic and assume that we are paused so that we can // respond to requests. @@ -2329,7 +2147,7 @@ PauseScopedActor.prototype = { /** * Returns the wrongState response packet for this actor. */ - _wrongState: function PSA_wrongState() { + _wrongState: function () { return { error: "wrongState", message: this.constructor.name + @@ -2338,38 +2156,92 @@ PauseScopedActor.prototype = { } }; +/** + * Resolve a URI back to physical file. + * + * Of course, this works only for URIs pointing to local resources. + * + * @param aURI + * URI to resolve + * @return + * resolved nsIURI + */ +function resolveURIToLocalPath(aURI) { + let resolved; + switch (aURI.scheme) { + case "jar": + case "file": + return aURI; + + case "chrome": + resolved = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry).convertChromeURL(aURI); + return resolveURIToLocalPath(resolved); + + case "resource": + resolved = Cc["@mozilla.org/network/protocol;1?name=resource"]. + getService(Ci.nsIResProtocolHandler).resolveURI(aURI); + aURI = Services.io.newURI(resolved, null, null); + return resolveURIToLocalPath(aURI); + + default: + return null; + } +} /** - * A SourceActor provides information about the source of a script. + * A SourceActor provides information about the source of a script. There + * are two kinds of source actors: ones that represent real source objects, + * and ones that represent non-existant "original" sources when the real + * sources are sourcemapped. When a source is sourcemapped, actors are + * created for both the "generated" and "original" sources, and the client will + * only see the original sources. We separate these because there isn't + * a 1:1 mapping of generated to original sources; one generated source + * may represent N original sources, so we need to create N + 1 separate + * actors. * - * @param String url - * The url of the source we are representing. + * There are 4 different scenarios for sources that you should + * understand: + * + * - A single non-sourcemapped source that is not inlined in HTML + * (separate JS file, eval'ed code, etc) + * - A single sourcemapped source which creates N original sources + * - An HTML page with multiple inline scripts, which are distinct + * sources, but should be represented as a single source + * - A pretty-printed source (which may or may not be an original + * sourcemapped source), which generates a sourcemap for itself + * + * The complexity of `SourceActor` and `ThreadSources` are to handle + * all of thise cases and hopefully internalize the complexities. + * + * @param Debugger.Source source + * The source object we are representing. * @param ThreadActor thread * The current thread actor. - * @param SourceMapConsumer sourceMap - * Optional. The source map that introduced this source, if available. - * @param String generatedSource + * @param String originalUrl + * Optional. For sourcemapped urls, the original url this is representing. + * @param Debugger.Source generatedSource * Optional, passed in when aSourceMap is also passed in. The generated - * source url that introduced this source. - * @param String text - * Optional. The content text of this source, if immediately available. + * source object that introduced this source. * @param String contentType * Optional. The content type of this source, if immediately available. */ -function SourceActor({ url, thread, sourceMap, generatedSource, text, - contentType }) { +function SourceActor({ source, thread, originalUrl, generatedSource, + isInlineSource, contentType }) { this._threadActor = thread; - this._url = url; - this._sourceMap = sourceMap; + this._originalUrl = originalUrl; + this._source = source; this._generatedSource = generatedSource; - this._text = text; this._contentType = contentType; + this._isInlineSource = isInlineSource; this.onSource = this.onSource.bind(this); this._invertSourceMap = this._invertSourceMap.bind(this); - this._saveMap = this._saveMap.bind(this); + this._encodeAndSetSourceMapURL = this._encodeAndSetSourceMapURL.bind(this); this._getSourceText = this._getSourceText.bind(this); + this._mapSourceToAddon(); + if (this.threadActor.sources.isPrettyPrinted(this.url)) { this._init = this.onPrettyPrint({ indent: this.threadActor.sources.prettyPrintIndent(this.url) @@ -2387,62 +2259,274 @@ SourceActor.prototype = { _oldSourceMap: null, _init: null, + _addonID: null, + _addonPath: null, - get threadActor() this._threadActor, - get url() this._url, + get isSourceMapped() { + return !this.isInlineSource && ( + this._originalURL || this._generatedSource || + this.threadActor.sources.isPrettyPrinted(this.url) + ); + }, + + get isInlineSource() { + return this._isInlineSource; + }, + + get threadActor() { return this._threadActor; }, + get sources() { return this._threadActor.sources; }, + get dbg() { return this.threadActor.dbg; }, + get scripts() { return this.threadActor.scripts; }, + get source() { return this._source; }, + get generatedSource() { return this._generatedSource; }, + get breakpointActorMap() { return this.threadActor.breakpointActorMap; }, + get url() { + if (this.source) { + return getSourceURL(this.source, this.threadActor._parent.window); + } + return this._originalUrl; + }, + get addonID() { return this._addonID; }, + get addonPath() { return this._addonPath; }, get prettyPrintWorker() { return this.threadActor.prettyPrintWorker; }, - form: function SA_form() { + form: function () { + let source = this.source || this.generatedSource; + // This might not have a source or a generatedSource because we + // treat HTML pages with inline scripts as a special SourceActor + // that doesn't have either + let introductionUrl = null; + if (source && source.introductionScript) { + introductionUrl = source.introductionScript.source.url; + } + return { actor: this.actorID, - url: this._url, + url: this.url ? this.url.split(" -> ").pop() : null, + addonID: this._addonID, + addonPath: this._addonPath, isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url), - isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url) - // TODO bug 637572: introductionScript + isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url), + introductionUrl: introductionUrl ? introductionUrl.split(" -> ").pop() : null, + introductionType: source ? source.introductionType : null }; }, - disconnect: function SA_disconnect() { + disconnect: function () { if (this.registeredPool && this.registeredPool.sourceActors) { delete this.registeredPool.sourceActors[this.actorID]; } }, - _getSourceText: function SA__getSourceText() { - const toResolvedContent = t => resolve({ + _mapSourceToAddon: function() { + try { + var nsuri = Services.io.newURI(this.url.split(" -> ").pop(), null, null); + } + catch (e) { + // We can't do anything with an invalid URI + return; + } + + let localURI = resolveURIToLocalPath(nsuri); + + let id = {}; + if (localURI && mapURIToAddonID(localURI, id)) { + this._addonID = id.value; + + if (localURI instanceof Ci.nsIJARURI) { + // The path in the add-on is easy for jar: uris + this._addonPath = localURI.JAREntry; + } + else if (localURI instanceof Ci.nsIFileURL) { + // For file: uris walk up to find the last directory that is part of the + // add-on + let target = localURI.file; + let path = target.leafName; + + // We can assume that the directory containing the source file is part + // of the add-on + let root = target.parent; + let file = root.parent; + while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) { + path = root.leafName + "/" + path; + root = file; + file = file.parent; + } + + if (!file) { + const error = new Error("Could not find the root of the add-on for " + this.url); + DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error) + return; + } + + this._addonPath = path; + } + } + }, + + _reportLoadSourceError: function (error, map=null) { + try { + DevToolsUtils.reportException("SourceActor", error); + + JSON.stringify(this.form(), null, 4).split(/\n/g) + .forEach(line => console.error("\t", line)); + + if (!map) { + return; + } + + console.error("\t", "source map's sourceRoot =", map.sourceRoot); + + console.error("\t", "source map's sources ="); + map.sources.forEach(s => { + let hasSourceContent = map.sourceContentFor(s, true); + console.error("\t\t", s, "\t", + hasSourceContent ? "has source content" : "no source content"); + }); + + console.error("\t", "source map's sourcesContent ="); + map.sourcesContent.forEach(c => { + if (c.length > 80) { + c = c.slice(0, 77) + "..."; + } + c = c.replace(/\n/g, "\\n"); + console.error("\t\t", c); + }); + } catch (e) { } + }, + + _getSourceText: function () { + let toResolvedContent = t => ({ content: t, contentType: this._contentType }); - let sc; - if (this._sourceMap && (sc = this._sourceMap.sourceContentFor(this._url))) { - return toResolvedContent(sc); + let genSource = this.generatedSource || this.source; + return this.threadActor.sources.fetchSourceMap(genSource).then(map => { + if (map) { + try { + let sourceContent = map.sourceContentFor(this.url); + if (sourceContent) { + return toResolvedContent(sourceContent); + } + } catch (error) { + this._reportLoadSourceError(error, map); + throw error; + } + } + + // Use `source.text` if it exists, is not the "no source" + // string, and the content type of the source is JavaScript. It + // will be "no source" if the Debugger API wasn't able to load + // the source because sources were discarded + // (javascript.options.discardSystemSource == true). Re-fetch + // non-JS sources to get the contentType from the headers. + if (this.source && + this.source.text !== "[no source]" && + this._contentType && + this._contentType.indexOf('javascript') !== -1) { + return toResolvedContent(this.source.text); + } + else { + // Only load the HTML page source from cache (which exists when + // there are inline sources). Otherwise, we can't trust the + // cache because we are most likely here because we are + // fetching the original text for sourcemapped code, and the + // page hasn't requested it before (if it has, it was a + // previous debugging session). + let sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource }); + + // Record the contentType we just learned during fetching + return sourceFetched + .then(result => { + this._contentType = result.contentType; + return result; + }, error => { + this._reportLoadSourceError(error, map); + throw error; + }); + } + }); + }, + + /** + * Get all executable lines from the current source + * @return Array - Executable lines of the current script + **/ + getExecutableLines: function () { + // Check if the original source is source mapped + let packet = { + from: this.actorID + }; + + function sortLines(lines) { + // Converting the Set into an array + lines = [...lines]; + lines.sort((a, b) => { + return a - b; + }); + return lines; } - if (this._text) { - return toResolvedContent(this._text); + if (this.generatedSource) { + return this.threadActor.sources.getSourceMap(this.generatedSource).then(sm => { + let lines = new Set(); + + // Position of executable lines in the generated source + let offsets = this.getExecutableOffsets(this.generatedSource, false); + for (let offset of offsets) { + let {line, source: sourceUrl} = sm.originalPositionFor({ + line: offset.lineNumber, + column: offset.columnNumber + }); + + if (sourceUrl === this.url) { + lines.add(line); + } + } + + packet.lines = sortLines(lines); + return packet; + }); } - // XXX bug 865252: Don't load from the cache if this is a source mapped - // source because we can't guarantee that the cache has the most up to date - // content for this source like we can if it isn't source mapped. - return fetch(this._url, { loadFromCache: !this._sourceMap }); + let lines = this.getExecutableOffsets(this.source, true); + packet.lines = sortLines(lines); + return packet; + }, + + /** + * Extract all executable offsets from the given script + * @param String url - extract offsets of the script with this url + * @param Boolean onlyLine - will return only the line number + * @return Set - Executable offsets/lines of the script + **/ + getExecutableOffsets: function (source, onlyLine) { + let offsets = new Set(); + for (let s of this.threadActor.scripts.getScriptsBySource(source)) { + for (let offset of s.getAllColumnOffsets()) { + offsets.add(onlyLine ? offset.lineNumber : offset); + } + } + + return offsets; }, /** * Handler for the "source" packet. */ - onSource: function SA_onSource() { + onSource: function () { return resolve(this._init) .then(this._getSourceText) .then(({ content, contentType }) => { return { from: this.actorID, - source: this.threadActor.createValueGrip( - content, this.threadActor.threadLifetimePool), + // source: createValueGrip(content, this.threadActor.threadLifetimePool, + // this.threadActor.objectGrip), + source: content, contentType: contentType }; }) @@ -2450,9 +2534,9 @@ SourceActor.prototype = { reportError(aError, "Got an exception during SA_onSource: "); return { "from": this.actorID, - "error": "loadSourceError", - "message": "Could not load the source for " + this._url + ".\n" - + safeErrorString(aError) + "error": this.url, + "message": "Could not load the source for " + this.url + ".\n" + + DevToolsUtils.safeErrorString(aError) }; }); }, @@ -2461,11 +2545,11 @@ SourceActor.prototype = { * Handler for the "prettyPrint" packet. */ onPrettyPrint: function ({ indent }) { - this.threadActor.sources.prettyPrint(this._url, indent); + this.threadActor.sources.prettyPrint(this.url, indent); return this._getSourceText() .then(this._sendToPrettyPrintWorker(indent)) .then(this._invertSourceMap) - .then(this._saveMap) + .then(this._encodeAndSetSourceMapURL) .then(() => { // We need to reset `_init` now because we have already done the work of // pretty printing, and don't want onSource to wait forever for @@ -2495,33 +2579,13 @@ SourceActor.prototype = { * is resolved with `{ code, mappings }` where `code` is the pretty * printed code, and `mappings` is an array of source mappings. */ - _sendToPrettyPrintWorker: function SA__sendToPrettyPrintWorker(aIndent) { + _sendToPrettyPrintWorker: function (aIndent) { return ({ content }) => { - const deferred = promise.defer(); - const id = Math.random(); - - const onReply = ({ data }) => { - if (data.id !== id) { - return; - } - this.prettyPrintWorker.removeEventListener("message", onReply, false); - - if (data.error) { - deferred.reject(new Error(data.error)); - } else { - deferred.resolve(data); - } - }; - - this.prettyPrintWorker.addEventListener("message", onReply, false); - this.prettyPrintWorker.postMessage({ - id: id, - url: this._url, + return this.prettyPrintWorker.performTask("pretty-print", { + url: this.url, indent: aIndent, source: content - }); - - return deferred.promise; + }) }; }, @@ -2533,49 +2597,27 @@ SourceActor.prototype = { * * Note that the source map is modified in place. */ - _invertSourceMap: function SA__invertSourceMap({ code, mappings }) { - const generator = new SourceMapGenerator({ file: this._url }); - return DevToolsUtils.yieldingEach(mappings, m => { + _invertSourceMap: function ({ code, mappings }) { + const generator = new SourceMapGenerator({ file: this.url }); + return DevToolsUtils.yieldingEach(mappings._array, m => { let mapping = { generated: { - line: m.generatedLine, - column: m.generatedColumn + line: m.originalLine, + column: m.originalColumn } }; if (m.source) { mapping.source = m.source; mapping.original = { - line: m.originalLine, - column: m.originalColumn + line: m.generatedLine, + column: m.generatedColumn }; mapping.name = m.name; } generator.addMapping(mapping); }).then(() => { - generator.setSourceContent(this._url, code); - const consumer = SourceMapConsumer.fromSourceMap(generator); - - // XXX bug 918802: Monkey punch the source map consumer, because iterating - // over all mappings and inverting each of them, and then creating a new - // SourceMapConsumer is slow. - - const getOrigPos = consumer.originalPositionFor.bind(consumer); - const getGenPos = consumer.generatedPositionFor.bind(consumer); - - consumer.originalPositionFor = ({ line, column }) => { - const location = getGenPos({ - line: line, - column: column, - source: this._url - }); - location.source = this._url; - return location; - }; - - consumer.generatedPositionFor = ({ line, column }) => getOrigPos({ - line: line, - column: column - }); + generator.setSourceContent(this.url, code); + let consumer = SourceMapConsumer.fromSourceMap(generator); return { code: code, @@ -2590,36 +2632,54 @@ SourceActor.prototype = { * pretty printing a source mapped source, we need to compose the existing * source map with our new one. */ - _saveMap: function SA__saveMap({ map }) { - if (this._sourceMap) { - // Compose the source maps - this._oldSourceMap = this._sourceMap; - this._sourceMap = SourceMapGenerator.fromSourceMap(this._sourceMap); - this._sourceMap.applySourceMap(map, this._url); - this._sourceMap = SourceMapConsumer.fromSourceMap(this._sourceMap); - this._threadActor.sources.saveSourceMap(this._sourceMap, - this._generatedSource); - } else { - this._sourceMap = map; - this._threadActor.sources.saveSourceMap(this._sourceMap, this._url); - } + _encodeAndSetSourceMapURL: function ({ map: sm }) { + let source = this.generatedSource || this.source; + let sources = this.threadActor.sources; + + return sources.getSourceMap(source).then(prevMap => { + if (prevMap) { + // Compose the source maps + this._oldSourceMapping = { + url: source.sourceMapURL, + map: prevMap + }; + + prevMap = SourceMapGenerator.fromSourceMap(prevMap); + prevMap.applySourceMap(sm, this.url); + sm = SourceMapConsumer.fromSourceMap(prevMap); + } + + let sources = this.threadActor.sources; + sources.clearSourceMapCache(source.sourceMapURL); + sources.setSourceMapHard(source, null, sm); + }); }, /** * Handler for the "disablePrettyPrint" packet. */ - onDisablePrettyPrint: function SA_onDisablePrettyPrint() { - this._sourceMap = this._oldSourceMap; - this.threadActor.sources.saveSourceMap(this._sourceMap, - this._generatedSource || this._url); - this.threadActor.sources.disablePrettyPrint(this._url); + onDisablePrettyPrint: function () { + let source = this.generatedSource || this.source; + let sources = this.threadActor.sources; + let sm = sources.getSourceMap(source); + + sources.clearSourceMapCache(source.sourceMapURL, { hard: true }); + + if (this._oldSourceMapping) { + sources.setSourceMapHard(source, + this._oldSourceMapping.url, + this._oldSourceMapping.map); + this._oldSourceMapping = null; + } + + this.threadActor.sources.disablePrettyPrint(this.url); return this.onSource(); }, /** * Handler for the "blackbox" packet. */ - onBlackBox: function SA_onBlackBox(aRequest) { + onBlackBox: function (aRequest) { this.threadActor.sources.blackBox(this.url); let packet = { from: this.actorID @@ -2635,11 +2695,420 @@ SourceActor.prototype = { /** * Handler for the "unblackbox" packet. */ - onUnblackBox: function SA_onUnblackBox(aRequest) { + onUnblackBox: function (aRequest) { this.threadActor.sources.unblackBox(this.url); return { from: this.actorID }; + }, + + /** + * Handle a request to set a breakpoint. + * + * @param JSON request + * A JSON object representing the request. + * + * @returns Promise + * A promise that resolves to a JSON object representing the + * response. + */ + onSetBreakpoint: function (request) { + if (this.threadActor.state !== "paused") { + return { + error: "wrongState", + message: "Cannot set breakpoint while debuggee is running." + }; + } + + let { location: { line, column }, condition } = request; + let location = new OriginalLocation(this, line, column); + return this._getOrCreateBreakpointActor( + location, + condition + ).then((actor) => { + let response = { + actor: actor.actorID, + isPending: actor.isPending + }; + + let actualLocation = actor.originalLocation; + if (!actualLocation.equals(location)) { + response.actualLocation = actualLocation.toJSON(); + } + + return response; + }); + }, + + /** + * Get or create a BreakpointActor for the given location in the original + * source, and ensure it is set as a breakpoint handler on all scripts that + * match the given location. + * + * @param OriginalLocation originalLocation + * An OriginalLocation representing the location of the breakpoint in + * the original source. + * @param String condition + * A string that is evaluated whenever the breakpoint is hit. If the + * string evaluates to false, the breakpoint is ignored. + * + * @returns BreakpointActor + * A BreakpointActor representing the breakpoint. + */ + _getOrCreateBreakpointActor: function (originalLocation, condition) { + let actor = this.breakpointActorMap.getActor(originalLocation); + if (!actor) { + actor = new BreakpointActor(this.threadActor, originalLocation); + this.threadActor.threadLifetimePool.addActor(actor); + this.breakpointActorMap.setActor(originalLocation, actor); + } + + actor.condition = condition; + + return this._setBreakpoint(actor); + }, + + /* + * Ensure the given BreakpointActor is set as a breakpoint handler on all + * scripts that match its location in the original source. + * + * If there are no scripts that match the location of the BreakpointActor, + * we slide its location to the next closest line (for line breakpoints) or + * column (for column breakpoint) that does. + * + * If breakpoint sliding fails, then either there are no scripts that contain + * any code for the given location, or they were all garbage collected before + * the debugger started running. We cannot distinguish between these two + * cases, so we insert the BreakpointActor in the BreakpointActorMap as + * a pending breakpoint. Whenever a new script is introduced, this method is + * called again for each pending breakpoint. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * + * @returns A Promise that resolves to the given BreakpointActor. + */ + _setBreakpoint: function (actor) { + let { originalLocation } = actor; + let { originalSourceActor, originalLine, originalColumn } = originalLocation; + + return this._setBreakpointAtOriginalLocation(actor, originalLocation) + .then((actualLocation) => { + if (actualLocation) { + return actualLocation; + } + + // There were no scripts that matched the given location, so we need to + // perform breakpoint sliding. We try to slide the breakpoint by column + // first, and if that fails, by line instead. + if (!this.isSourceMapped) { + if (originalColumn !== undefined) { + // To perform breakpoint sliding for column breakpoints, we need to + // build a map from column numbers to a list of entry points for each + // column, implemented as a sparse array. An entry point is a (script, + // offsets) pair, and represents all offsets in that script that are + // entry points for the corresponding column. + let columnToEntryPointsMap = []; + + // Iterate over all scripts that correspond to this source actor and + // line number. + let scripts = this.scripts.getScriptsBySourceActor(this, originalLine); + for (let script of scripts) { + let columnToOffsetMap = script.getAllColumnOffsets() + .filter(({ lineNumber }) => { + return lineNumber === originalLine; + }) + + // Iterate over each column, and add their list of offsets to the + // map from column numbers to entry points by forming a (script, + // offsets) pair, where script is the current script, and offsets is + // the list of offsets for the current column. + for (let { columnNumber: column, offset } of columnToOffsetMap) { + let entryPoints = columnToEntryPointsMap[column]; + if (!entryPoints) { + // We dont have a list of entry points for the current column + // number yet, so create it and add it to the map. + entryPoints = []; + columnToEntryPointsMap[column] = entryPoints; + } + entryPoints.push({ script, offsets: [offset] }); + } + } + + // Now that we have a map from column numbers to a list of entry points + // for each column, we can use it to perform breakpoint sliding. Start + // at the original column of the breakpoint actor, and keep + // incrementing it by one, until either we find a line that has at + // least one entry point, or we go past the last column in the map. + // + // Note that by computing the entire map up front, and implementing it + // as a sparse array, we can easily tell when we went past the last + // column in the map. + let actualColumn = originalColumn + 1; + while (actualColumn < columnToEntryPointsMap.length) { + let entryPoints = columnToEntryPointsMap[actualColumn]; + if (entryPoints) { + setBreakpointAtEntryPoints(actor, entryPoints); + return new OriginalLocation( + originalSourceActor, + originalLine, + actualColumn + ); + } + ++actualColumn; + } + + return originalLocation; + } else { + // To perform breakpoint sliding for line breakpoints, we need to + // build a map from line numbers to a list of entry points for each + // line, implemented as a sparse array. An entry point is a (script, + // offsets) pair, and represents all offsets in that script that are + // entry points for the corresponding line. + let lineToEntryPointsMap = []; + + // Iterate over all scripts that correspond to this source actor. + let scripts = this.scripts.getScriptsBySourceActor(this); + for (let script of scripts) { + // Get all offsets for each line in the current script. This returns + // a map from line numbers fo a list of offsets for each line, + // implemented as a sparse array. + let lineToOffsetsMap = script.getAllOffsets(); + + // Iterate over each line, and add their list of offsets to the map + // from line numbers to entry points by forming a (script, offsets) + // pair, where script is the current script, and offsets is the list + // of offsets for the current line. + for (let line = 0; line < lineToOffsetsMap.length; ++line) { + let offsets = lineToOffsetsMap[line]; + if (offsets) { + let entryPoints = lineToEntryPointsMap[line]; + if (!entryPoints) { + // We dont have a list of entry points for the current line + // number yet, so create it and add it to the map. + entryPoints = []; + lineToEntryPointsMap[line] = entryPoints; + } + entryPoints.push({ script, offsets }); + } + } + } + + // Now that we have a map from line numbers to a list of entry points + // for each line, we can use it to perform breakpoint sliding. Start + // at the original line of the breakpoint actor, and keep incrementing + // it by one, until either we find a line that has at least one entry + // point, or we go past the last line in the map. + // + // Note that by computing the entire map up front, and implementing it + // as a sparse array, we can easily tell when we went past the last + // line in the map. + let actualLine = originalLine + 1; + while (actualLine < lineToEntryPointsMap.length) { + let entryPoints = lineToEntryPointsMap[actualLine]; + if (entryPoints) { + setBreakpointAtEntryPoints(actor, entryPoints); + break; + } + ++actualLine; + } + if (actualLine >= lineToEntryPointsMap.length) { + // We went past the last line in the map, so breakpoint sliding + // failed. Keep the BreakpointActor in the BreakpointActorMap as a + // pending breakpoint, so we can try again whenever a new script is + // introduced. + return originalLocation; + } + + return new OriginalLocation( + originalSourceActor, + actualLine + ); + } + } else { + let slideByColumn = (actualColumn) => { + return this.sources.getAllGeneratedLocations(new OriginalLocation( + this, + originalLine, + actualColumn + )).then((generatedLocations) => { + // Because getAllGeneratedLocations will always return the list of + // generated locations for the closest column that is greater than + // the one we are searching for if no exact match can be found, if + // the list of generated locations is empty, we've reached the end + // of the original line, and sliding continues by line. + if (generatedLocations.length === 0) { + return slideByLine(originalLine + 1); + } + + // If at least one script has an offset that matches one of the + // generated locations in the list, then breakpoint sliding + // succeeded. + if (this._setBreakpointAtAllGeneratedLocations(actor, generatedLocations)) { + return this.threadActor.sources.getOriginalLocation(generatedLocations[0]); + } + + // Try the next column in the original source. + return slideByColumn(actualColumn + 1); + }); + }; + + let slideByLine = (actualLine) => { + return this.sources.getAllGeneratedLocations(new OriginalLocation( + this, + actualLine + )).then((generatedLocations) => { + // Because getAllGeneratedLocations will always return the list of + // generated locations for the closest line that is greater than + // the one we are searching for if no exact match can be found, if + // the list of generated locations is empty, we've reached the end + // of the original source, and breakpoint sliding failed. + if (generatedLocations.length === 0) { + return originalLocation; + } + + // If at least one script has an offset that matches one of the + // generated locations in the list, then breakpoint sliding + // succeeded. + if (this._setBreakpointAtAllGeneratedLocations(actor, generatedLocations)) { + return this.threadActor.sources.getOriginalLocation(generatedLocations[0]); + } + + // Try the next line in the original source. + return slideByLine(actualLine + 1); + }); + }; + + if (originalColumn !== undefined) { + return slideByColumn(originalColumn + 1); + } else { + return slideByLine(originalLine + 1); + } + } + }).then((actualLocation) => { + // If the actual location on which the BreakpointActor ended up being + // set differs from the original line that was requested, both the + // BreakpointActor and the BreakpointActorMap need to be updated + // accordingly. + if (!actualLocation.equals(originalLocation)) { + let existingActor = this.breakpointActorMap.getActor(actualLocation); + if (existingActor) { + actor.onDelete(); + this.breakpointActorMap.deleteActor(originalLocation); + actor = existingActor; + } else { + this.breakpointActorMap.deleteActor(originalLocation); + actor.originalLocation = actualLocation; + this.breakpointActorMap.setActor(actualLocation, actor); + } + } + + return actor; + }); + }, + + _setBreakpointAtOriginalLocation: function (actor, originalLocation) { + if (!this.isSourceMapped) { + if (!this._setBreakpointAtGeneratedLocation( + actor, + GeneratedLocation.fromOriginalLocation(originalLocation) + )) { + // return promise.resolve(null); + return resolve(null); + } + + // return promise.resolve(originalLocation); + return resolve(originalLocation); + } else { + return this.sources.getAllGeneratedLocations(originalLocation) + .then((generatedLocations) => { + if (!this._setBreakpointAtAllGeneratedLocations( + actor, + generatedLocations + )) { + return null; + } + + return this.threadActor.sources.getOriginalLocation(generatedLocations[0]); + }); + } + }, + + _setBreakpointAtAllGeneratedLocations: function (actor, generatedLocations) { + let success = false; + for (let generatedLocation of generatedLocations) { + if (this._setBreakpointAtGeneratedLocation( + actor, + generatedLocation + )) { + success = true; + } + } + return success; + }, + + /* + * Ensure the given BreakpointActor is set as breakpoint handler on all + * scripts that match the given location in the generated source. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * @param GeneratedLocation generatedLocation + * A GeneratedLocation representing the location in the generated + * source for which the given BreakpointActor is to be set as a + * breakpoint handler. + * + * @returns A Boolean that is true if the BreakpointActor was set as a + * breakpoint handler on at least one script, and false otherwise. + */ + _setBreakpointAtGeneratedLocation: function (actor, generatedLocation) { + let { + generatedSourceActor, + generatedLine, + generatedColumn, + generatedLastColumn + } = generatedLocation; + + // Find all scripts that match the given source actor and line number. + let scripts = this.scripts.getScriptsBySourceActorAndLine( + generatedSourceActor, + generatedLine + ); + + scripts = scripts.filter((script) => !actor.hasScript(script)); + + // Find all entry points that correspond to the given location. + let entryPoints = []; + if (generatedColumn === undefined) { + // This is a line breakpoint, so we are interested in all offsets + // that correspond to the given line number. + for (let script of scripts) { + let offsets = script.getLineOffsets(generatedLine); + if (offsets.length > 0) { + entryPoints.push({ script, offsets }); + } + } + } else { + // This is a column breakpoint, so we are interested in all column + // offsets that correspond to the given line *and* column number. + for (let script of scripts) { + let columnToOffsetMap = script.getAllColumnOffsets() + .filter(({ lineNumber }) => { + return lineNumber === generatedLine; + }); + for (let { columnNumber: column, offset } of columnToOffsetMap) { + if (column >= generatedColumn && column <= generatedLastColumn) { + entryPoints.push({ script, offsets: [offset] }); + } + } + } + } + + if (entryPoints.length === 0) { + return false; + } + setBreakpointAtEntryPoints(actor, entryPoints); + return true; } }; @@ -2648,456 +3117,21 @@ SourceActor.prototype.requestTypes = { "blackbox": SourceActor.prototype.onBlackBox, "unblackbox": SourceActor.prototype.onUnblackBox, "prettyPrint": SourceActor.prototype.onPrettyPrint, - "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint -}; - - -/** - * Creates an actor for the specified object. - * - * @param aObj Debugger.Object - * The debuggee object. - * @param aThreadActor ThreadActor - * The parent thread actor for this object. - */ -function ObjectActor(aObj, aThreadActor) -{ - this.obj = aObj; - this.threadActor = aThreadActor; -} - -ObjectActor.prototype = { - actorPrefix: "obj", - - _forcedMagicProps: false, - - /** - * Returns a grip for this actor for returning in a protocol message. - */ - grip: function OA_grip() { - let g = { - "type": "object", - "class": this.obj.class, - "actor": this.actorID, - "extensible": this.obj.isExtensible && typeof this.obj.isExtensible == 'function' ? this.obj.isExtensible() : true, - "frozen": this.obj.isFrozen && typeof this.obj.isFrozen == 'function' ? this.obj.isFrozen() : false, - "sealed": this.obj.isSealed && typeof this.obj.isSealed == 'function' ? this.obj.isSealed() : false - }; - - // Add additional properties for functions. - if (this.obj.class === "Function") { - if (this.obj.name) { - g.name = this.obj.name; - } - if (this.obj.displayName) { - g.displayName = this.obj.displayName; - } - - // Check if the developer has added a de-facto standard displayName - // property for us to use. - try { - let desc = this.obj.getOwnPropertyDescriptor("displayName"); - if (desc && desc.value && typeof desc.value == "string") { - g.userDisplayName = this.threadActor.createValueGrip(desc.value); - } - } catch (e) { - // Calling getOwnPropertyDescriptor with displayName might throw - // with "permission denied" errors for some functions. - dumpn(e); - } - - // Add source location information. - if (this.obj.script) { - g.url = this.obj.script.url; - g.line = this.obj.script.startLine; - } - } - - return g; - }, - - /** - * Releases this actor from the pool. - */ - release: function OA_release() { - if (this.registeredPool.objectActors) { - this.registeredPool.objectActors.delete(this.obj); - } - this.registeredPool.removeActor(this); - }, - - /** - * Force the magic Error properties to appear. - */ - _forceMagicProperties: function OA__forceMagicProperties() { - if (this._forcedMagicProps) { - return; - } - - const MAGIC_ERROR_PROPERTIES = [ - "message", "stack", "fileName", "lineNumber", "columnNumber" - ]; - - if (this.obj.class.endsWith("Error")) { - for (let property of MAGIC_ERROR_PROPERTIES) { - this._propertyDescriptor(property); - } - } - - this._forcedMagicProps = true; - }, - - /** - * Handle a protocol request to provide the names of the properties defined on - * the object and not its prototype. - * - * @param aRequest object - * The protocol request object. - */ - onOwnPropertyNames: function OA_onOwnPropertyNames(aRequest) { - this._forceMagicProperties(); - return { from: this.actorID, - ownPropertyNames: this.obj.getOwnPropertyNames() }; - }, - - /** - * Handle a protocol request to provide the prototype and own properties of - * the object. - * - * @param aRequest object - * The protocol request object. - */ - onPrototypeAndProperties: function OA_onPrototypeAndProperties(aRequest) { - this._forceMagicProperties(); - let ownProperties = Object.create(null); - let names; - try { - names = this.obj.getOwnPropertyNames(); - } catch (ex) { - // The above can throw if this.obj points to a dead object. - // TODO: we should use Cu.isDeadWrapper() - see bug 885800. - return { from: this.actorID, - prototype: this.threadActor.createValueGrip(null), - ownProperties: ownProperties, - safeGetterValues: Object.create(null) }; - } - for (let name of names) { - ownProperties[name] = this._propertyDescriptor(name); - } - return { from: this.actorID, - prototype: this.threadActor.createValueGrip(this.obj.proto), - ownProperties: ownProperties, - safeGetterValues: this._findSafeGetterValues(ownProperties) }; - }, - - /** - * Find the safe getter values for the current Debugger.Object, |this.obj|. - * - * @private - * @param object aOwnProperties - * The object that holds the list of known ownProperties for - * |this.obj|. - * @return object - * An object that maps property names to safe getter descriptors as - * defined by the remote debugging protocol. - */ - _findSafeGetterValues: function OA__findSafeGetterValues(aOwnProperties) - { - let safeGetterValues = Object.create(null); - let obj = this.obj; - let level = 0; - - while (obj) { - let getters = this._findSafeGetters(obj); - for (let name of getters) { - // Avoid overwriting properties from prototypes closer to this.obj. Also - // avoid providing safeGetterValues from prototypes if property |name| - // is already defined as an own property. - if (name in safeGetterValues || - (obj != this.obj && name in aOwnProperties)) { - continue; - } - - let desc = null, getter = null; - try { - desc = obj.getOwnPropertyDescriptor(name); - getter = desc.get; - } catch (ex) { - // The above can throw if the cache becomes stale. - } - if (!getter) { - obj._safeGetters = null; - continue; - } - - let result = getter.call(this.obj); - if (result && !("throw" in result)) { - let getterValue = undefined; - if ("return" in result) { - getterValue = result.return; - } else if ("yield" in result) { - getterValue = result.yield; - } - // WebIDL attributes specified with the LenientThis extended attribute - // return undefined and should be ignored. - if (getterValue !== undefined) { - safeGetterValues[name] = { - getterValue: this.threadActor.createValueGrip(getterValue), - getterPrototypeLevel: level, - enumerable: desc.enumerable, - writable: level == 0 ? desc.writable : true, - }; - } - } - } - - obj = obj.proto; - level++; - } - - return safeGetterValues; - }, - - /** - * Find the safe getters for a given Debugger.Object. Safe getters are native - * getters which are safe to execute. - * - * @private - * @param Debugger.Object aObject - * The Debugger.Object where you want to find safe getters. - * @return Set - * A Set of names of safe getters. This result is cached for each - * Debugger.Object. - */ - _findSafeGetters: function OA__findSafeGetters(aObject) - { - if (aObject._safeGetters) { - return aObject._safeGetters; - } - - let getters = new Set(); - for (let name of aObject.getOwnPropertyNames()) { - let desc = null; - try { - desc = aObject.getOwnPropertyDescriptor(name); - } catch (e) { - // Calling getOwnPropertyDescriptor on wrapped native prototypes is not - // allowed (bug 560072). - } - if (!desc || desc.value !== undefined || !("get" in desc)) { - continue; - } - - let fn = desc.get; - if (fn && fn.callable && fn.class == "Function" && - fn.script === undefined) { - getters.add(name); - } - } - - aObject._safeGetters = getters; - return getters; - }, - - /** - * Handle a protocol request to provide the prototype of the object. - * - * @param aRequest object - * The protocol request object. - */ - onPrototype: function OA_onPrototype(aRequest) { - return { from: this.actorID, - prototype: this.threadActor.createValueGrip(this.obj.proto) }; - }, - - /** - * Handle a protocol request to provide the property descriptor of the - * object's specified property. - * - * @param aRequest object - * The protocol request object. - */ - onProperty: function OA_onProperty(aRequest) { - if (!aRequest.name) { - return { error: "missingParameter", - message: "no property name was specified" }; - } - - return { from: this.actorID, - descriptor: this._propertyDescriptor(aRequest.name) }; - }, - - /** - * Handle a protocol request to provide the display string for the object. - * - * @param aRequest object - * The protocol request object. - */ - onDisplayString: function OA_onDisplayString(aRequest) { - let toString; - try { - // Attempt to locate the object's "toString" method. - let obj = this.obj; - do { - let desc = obj.getOwnPropertyDescriptor("toString"); - if (desc) { - toString = desc.value; - break; - } - obj = obj.proto; - } while ((obj)); - } catch (e) { - dumpn(e); - } - - let result = null; - if (toString && toString.callable) { - // If a toString method was found then call it on the object. - let ret = toString.call(this.obj).return; - if (typeof ret == "string") { - // Only use the result if it was a returned string. - result = ret; - } - } - - return { from: this.actorID, - displayString: this.threadActor.createValueGrip(result) }; - }, - - /** - * A helper method that creates a property descriptor for the provided object, - * properly formatted for sending in a protocol response. - * - * @param string aName - * The property that the descriptor is generated for. - */ - _propertyDescriptor: function OA_propertyDescriptor(aName) { - let desc; - try { - desc = this.obj.getOwnPropertyDescriptor(aName); - } catch (e) { - // Calling getOwnPropertyDescriptor on wrapped native prototypes is not - // allowed (bug 560072). Inform the user with a bogus, but hopefully - // explanatory, descriptor. - return { - configurable: false, - writable: false, - enumerable: false, - value: e.name - }; - } - - if (!desc) { - return undefined; - } - - let retval = { - configurable: desc.configurable, - enumerable: desc.enumerable - }; - - if ("value" in desc) { - retval.writable = desc.writable; - retval.value = this.threadActor.createValueGrip(desc.value); - } else { - if ("get" in desc) { - retval.get = this.threadActor.createValueGrip(desc.get); - } - if ("set" in desc) { - retval.set = this.threadActor.createValueGrip(desc.set); - } - } - return retval; - }, - - /** - * Handle a protocol request to provide the source code of a function. - * - * @param aRequest object - * The protocol request object. - */ - onDecompile: function OA_onDecompile(aRequest) { - if (this.obj.class !== "Function") { - return { error: "objectNotFunction", - message: "decompile request is only valid for object grips " + - "with a 'Function' class." }; - } - - return { from: this.actorID, - decompiledCode: this.obj.decompile(!!aRequest.pretty) }; - }, - - /** - * Handle a protocol request to provide the parameters of a function. - * - * @param aRequest object - * The protocol request object. - */ - onParameterNames: function OA_onParameterNames(aRequest) { - if (this.obj.class !== "Function") { - return { error: "objectNotFunction", - message: "'parameterNames' request is only valid for object " + - "grips with a 'Function' class." }; - } - - return { parameterNames: this.obj.parameterNames }; - }, - - /** - * Handle a protocol request to release a thread-lifetime grip. - * - * @param aRequest object - * The protocol request object. - */ - onRelease: function OA_onRelease(aRequest) { - this.release(); - return {}; - }, - - /** - * Handle a protocol request to provide the lexical scope of a function. - * - * @param aRequest object - * The protocol request object. - */ - onScope: function OA_onScope(aRequest) { - if (this.obj.class !== "Function") { - return { error: "objectNotFunction", - message: "scope request is only valid for object grips with a" + - " 'Function' class." }; - } - - let envActor = this.threadActor.createEnvironmentActor(this.obj.environment, - this.registeredPool); - if (!envActor) { - return { error: "notDebuggee", - message: "cannot access the environment of this function." }; - } - - return { from: this.actorID, scope: envActor.form() }; - } -}; - -ObjectActor.prototype.requestTypes = { - "parameterNames": ObjectActor.prototype.onParameterNames, - "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties, - "prototype": ObjectActor.prototype.onPrototype, - "property": ObjectActor.prototype.onProperty, - "displayString": ObjectActor.prototype.onDisplayString, - "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames, - "decompile": ObjectActor.prototype.onDecompile, - "release": ObjectActor.prototype.onRelease, - "scope": ObjectActor.prototype.onScope, + "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint, + "getExecutableLines": SourceActor.prototype.getExecutableLines, + "setBreakpoint": SourceActor.prototype.onSetBreakpoint }; +exports.SourceActor = SourceActor; /** * Creates a pause-scoped actor for the specified object. * @see ObjectActor */ -function PauseScopedObjectActor() -{ - ObjectActor.apply(this, arguments); +function PauseScopedObjectActor(obj, hooks) { + ObjectActor.call(this, obj, hooks); + this.hooks.promote = hooks.promote; + this.hooks.isThreadLifetimePool = hooks.isThreadLifetimePool; } PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype); @@ -3131,8 +3165,8 @@ update(PauseScopedObjectActor.prototype, { * @param aRequest object * The protocol request object. */ - onThreadGrip: PauseScopedActor.withPaused(function OA_onThreadGrip(aRequest) { - this.threadActor.threadObjectGrip(this); + onThreadGrip: PauseScopedActor.withPaused(function (aRequest) { + this.hooks.promote(); return {}; }), @@ -3142,8 +3176,8 @@ update(PauseScopedObjectActor.prototype, { * @param aRequest object * The protocol request object. */ - onRelease: PauseScopedActor.withPaused(function OA_onRelease(aRequest) { - if (this.registeredPool !== this.threadActor.threadLifetimePool) { + onRelease: PauseScopedActor.withPaused(function (aRequest) { + if (this.hooks.isThreadLifetimePool()) { return { error: "notReleasable", message: "Only thread-lifetime actors can be released." }; } @@ -3157,80 +3191,6 @@ update(PauseScopedObjectActor.prototype.requestTypes, { "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip, }); - -/** - * Creates an actor for the specied "very long" string. "Very long" is specified - * at the server's discretion. - * - * @param aString String - * The string. - */ -function LongStringActor(aString) -{ - this.string = aString; - this.stringLength = aString.length; -} - -LongStringActor.prototype = { - - actorPrefix: "longString", - - disconnect: function LSA_disconnect() { - // Because longStringActors is not a weak map, we won't automatically leave - // it so we need to manually leave on disconnect so that we don't leak - // memory. - if (this.registeredPool && this.registeredPool.longStringActors) { - delete this.registeredPool.longStringActors[this.actorID]; - } - }, - - /** - * Returns a grip for this actor for returning in a protocol message. - */ - grip: function LSA_grip() { - return { - "type": "longString", - "initial": this.string.substring( - 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH), - "length": this.stringLength, - "actor": this.actorID - }; - }, - - /** - * Handle a request to extract part of this actor's string. - * - * @param aRequest object - * The protocol request object. - */ - onSubstring: function LSA_onSubString(aRequest) { - return { - "from": this.actorID, - "substring": this.string.substring(aRequest.start, aRequest.end) - }; - }, - - /** - * Handle a request to release this LongStringActor instance. - */ - onRelease: function LSA_onRelease() { - // TODO: also check if registeredPool === threadActor.threadLifetimePool - // when the web console moves aray from manually releasing pause-scoped - // actors. - if (this.registeredPool.longStringActors) { - delete this.registeredPool.longStringActors[this.actorID]; - } - this.registeredPool.removeActor(this); - return {}; - }, -}; - -LongStringActor.prototype.requestTypes = { - "substring": LongStringActor.prototype.onSubstring, - "release": LongStringActor.prototype.onRelease -}; - - /** * Creates an actor for the specified stack frame. * @@ -3264,7 +3224,7 @@ FrameActor.prototype = { * Finalization handler that is called when the actor is being evicted from * the pool. */ - disconnect: function FA_disconnect() { + disconnect: function () { this.conn.removeActorPool(this._frameLifetimePool); this._frameLifetimePool = null; }, @@ -3272,23 +3232,32 @@ FrameActor.prototype = { /** * Returns a frame form for use in a protocol message. */ - form: function FA_form() { + form: function () { + let threadActor = this.threadActor; let form = { actor: this.actorID, type: this.frame.type }; if (this.frame.type === "call") { - form.callee = this.threadActor.createValueGrip(this.frame.callee); + form.callee = createValueGrip(this.frame.callee, threadActor._pausePool, + threadActor.objectGrip); } if (this.frame.environment) { - let envActor = this.threadActor - .createEnvironmentActor(this.frame.environment, - this.frameLifetimePool); + let envActor = threadActor.createEnvironmentActor( + this.frame.environment, + this.frameLifetimePool + ); form.environment = envActor.form(); } - form.this = this.threadActor.createValueGrip(this.frame.this); + form.this = createValueGrip(this.frame.this, threadActor._pausePool, + threadActor.objectGrip); form.arguments = this._args(); if (this.frame.script) { - form.where = getFrameLocation(this.frame); + var generatedLocation = this.threadActor.sources.getFrameLocation(this.frame); + form.where = { + source: generatedLocation.generatedSourceActor.form(), + line: generatedLocation.generatedLine, + column: generatedLocation.generatedColumn + }; } if (!this.frame.older) { @@ -3298,13 +3267,13 @@ FrameActor.prototype = { return form; }, - _args: function FA__args() { + _args: function () { if (!this.frame.arguments) { return []; } - return [this.threadActor.createValueGrip(arg) - for each (arg in this.frame.arguments)]; + return this.frame.arguments.map(arg => createValueGrip(arg, + this.threadActor._pausePool, this.threadActor.objectGrip)); }, /** @@ -3313,7 +3282,7 @@ FrameActor.prototype = { * @param aRequest object * The protocol request object. */ - onPop: function FA_onPop(aRequest) { + onPop: function (aRequest) { // TODO: remove this when Debugger.Frame.prototype.pop is implemented if (typeof this.frame.pop != "function") { return { error: "notImplemented", @@ -3343,31 +3312,45 @@ FrameActor.prototype.requestTypes = { * * @param ThreadActor aThreadActor * The parent thread actor that contains this breakpoint. - * @param object aLocation - * The location of the breakpoint as specified in the protocol. + * @param OriginalLocation aOriginalLocation + * The original location of the breakpoint. */ -function BreakpointActor(aThreadActor, aLocation) +function BreakpointActor(aThreadActor, aOriginalLocation) { - this.scripts = []; + // The set of Debugger.Script instances that this breakpoint has been set + // upon. + this.scripts = new Set(); + this.threadActor = aThreadActor; - this.location = aLocation; + this.originalLocation = aOriginalLocation; + this.condition = null; + this.isPending = true; } BreakpointActor.prototype = { actorPrefix: "breakpoint", + condition: null, + + disconnect: function () { + this.removeScripts(); + }, + + hasScript: function (aScript) { + return this.scripts.has(aScript); + }, /** * Called when this same breakpoint is added to another Debugger.Script - * instance, in the case of a page reload. + * instance. * * @param aScript Debugger.Script * The new source script on which the breakpoint has been set. * @param ThreadActor aThreadActor * The parent thread actor that contains this breakpoint. */ - addScript: function BA_addScript(aScript, aThreadActor) { - this.threadActor = aThreadActor; - this.scripts.push(aScript); + addScript: function (aScript) { + this.scripts.add(aScript); + this.isPending = false; }, /** @@ -3377,7 +3360,49 @@ BreakpointActor.prototype = { for (let script of this.scripts) { script.clearBreakpoint(this); } - this.scripts = []; + this.scripts.clear(); + }, + + /** + * Check if this breakpoint has a condition that doesn't error and + * evaluates to true in aFrame. + * + * @param aFrame Debugger.Frame + * The frame to evaluate the condition in + * @returns Object + * - result: boolean|undefined + * True when the conditional breakpoint should trigger a pause, false otherwise. + * If the condition evaluation failed/killed, `result` will be `undefined`. + * - message: string + * The thrown message converted to a string, when the condition throws. + */ + checkCondition: function(aFrame) { + let completion = aFrame.eval(this.condition); + if (completion) { + if (completion.throw) { + // The evaluation failed and threw + let message = "Unknown exception"; + try { + if (completion.throw.getOwnPropertyDescriptor) { + message = completion.throw.getOwnPropertyDescriptor("message").value; + } else if (completion.toString) { + message = completion.toString(); + } + } catch (ex) {} + return { + result: true, + message: message + }; + } else if (completion.yield) { + dbg_assert(false, + "Shouldn't ever get yield completions from an eval"); + } else { + return { result: completion.return ? true : false }; + } + } else { + // The evaluation was killed (possibly by the slow script dialog) + return { result: undefined }; + } }, /** @@ -3386,28 +3411,43 @@ BreakpointActor.prototype = { * @param aFrame Debugger.Frame * The stack frame that contained the breakpoint. */ - hit: function BA_hit(aFrame) { + hit: function (aFrame) { // Don't pause if we are currently stepping (in or over) or the frame is // black-boxed. - let { url } = this.threadActor.synchronize( - this.threadActor.sources.getOriginalLocation({ - url: this.location.url, - line: this.location.line, - column: this.location.column - })); + let generatedLocation = this.threadActor.sources.getFrameLocation(aFrame); + let { originalSourceActor } = this.threadActor.unsafeSynchronize( + this.threadActor.sources.getOriginalLocation(generatedLocation)); + let url = originalSourceActor.url; - if (this.threadActor.sources.isBlackBoxed(url) || aFrame.onStep) { + if (this.threadActor.sources.isBlackBoxed(url) + || aFrame.onStep) { return undefined; } let reason = {}; + if (this.threadActor._hiddenBreakpoints.has(this.actorID)) { reason.type = "pauseOnDOMEvents"; - } else { + } else if (!this.condition) { reason.type = "breakpoint"; // TODO: add the rest of the breakpoints on that line (bug 676602). reason.actors = [ this.actorID ]; + } else { + let { result, message } = this.checkCondition(aFrame) + + if (result) { + if (!message) { + reason.type = "breakpoint"; + } else { + reason.type = "breakpointConditionThrown"; + reason.message = message; + } + reason.actors = [ this.actorID ]; + } else { + return undefined; + } } + return this.threadActor._pauseAndRespond(aFrame, reason); }, @@ -3417,10 +3457,12 @@ BreakpointActor.prototype = { * @param aRequest object * The protocol request object. */ - onDelete: function BA_onDelete(aRequest) { + onDelete: function (aRequest) { // Remove from the breakpoint store. - this.threadActor.breakpointStore.removeBreakpoint(this.location); - this.threadActor._hooks.removeFromParentPool(this); + if (this.originalLocation) { + this.threadActor.breakpointActorMap.deleteActor(this.originalLocation); + } + this.threadActor.threadLifetimePool.removeActor(this); // Remove the actual breakpoint from the associated scripts. this.removeScripts(); return { from: this.actorID }; @@ -3431,7 +3473,6 @@ BreakpointActor.prototype.requestTypes = { "delete": BreakpointActor.prototype.onDelete }; - /** * Creates an EnvironmentActor. EnvironmentActors are responsible for listing * the bindings introduced by a lexical environment and assigning new values to @@ -3454,7 +3495,7 @@ EnvironmentActor.prototype = { /** * Return an environment form for use in a protocol message. */ - form: function EA_form() { + form: function () { let form = { actor: this.actorID }; // What is this environment's type? @@ -3474,12 +3515,14 @@ EnvironmentActor.prototype = { // Does this environment reflect the properties of an object as variables? if (this.obj.type == "object" || this.obj.type == "with") { - form.object = this.threadActor.createValueGrip(this.obj.object); + form.object = createValueGrip(this.obj.object, + this.registeredPool, this.threadActor.objectGrip); } // Is this the environment created for a function call? if (this.obj.callee) { - form.function = this.threadActor.createValueGrip(this.obj.callee); + form.function = createValueGrip(this.obj.callee, + this.registeredPool, this.threadActor.objectGrip); } // Shall we list this environment's bindings? @@ -3494,7 +3537,7 @@ EnvironmentActor.prototype = { * Return the identifier bindings object as required by the remote protocol * specification. */ - _bindings: function EA_bindings() { + _bindings: function () { let bindings = { arguments: [], variables: {} }; // TODO: this part should be removed in favor of the commented-out part @@ -3507,15 +3550,19 @@ EnvironmentActor.prototype = { let parameterNames; if (this.obj.callee) { parameterNames = this.obj.callee.parameterNames; + } else { + parameterNames = []; } - for each (let name in parameterNames) { + for (let name of parameterNames) { let arg = {}; + let value = this.obj.getVariable(name); + // TODO: this part should be removed in favor of the commented-out part // below when getVariableDescriptor lands (bug 725815). let desc = { - value: this.obj.getVariable(name), + value: value, configurable: false, - writable: true, + writable: !(value && value.optimizedOut), enumerable: true }; @@ -3525,50 +3572,54 @@ EnvironmentActor.prototype = { configurable: desc.configurable }; if ("value" in desc) { - descForm.value = this.threadActor.createValueGrip(desc.value); + descForm.value = createValueGrip(desc.value, + this.registeredPool, this.threadActor.objectGrip); descForm.writable = desc.writable; } else { - descForm.get = this.threadActor.createValueGrip(desc.get); - descForm.set = this.threadActor.createValueGrip(desc.set); + descForm.get = createValueGrip(desc.get, this.registeredPool, + this.threadActor.objectGrip); + descForm.set = createValueGrip(desc.set, this.registeredPool, + this.threadActor.objectGrip); } arg[name] = descForm; bindings.arguments.push(arg); } - for each (let name in this.obj.names()) { + for (let name of this.obj.names()) { if (bindings.arguments.some(function exists(element) { return !!element[name]; })) { continue; } + let value = this.obj.getVariable(name); + // TODO: this part should be removed in favor of the commented-out part // below when getVariableDescriptor lands. let desc = { + value: value, configurable: false, - writable: true, + writable: !(value && + (value.optimizedOut || + value.uninitialized || + value.missingArguments)), enumerable: true }; - try { - desc.value = this.obj.getVariable(name); - } catch (e) { - // Avoid "Debugger scope is not live" errors for |arguments|, introduced - // in bug 746601. - if (name != "arguments") { - throw e; - } - } + //let desc = this.obj.getVariableDescriptor(name); let descForm = { enumerable: true, configurable: desc.configurable }; if ("value" in desc) { - descForm.value = this.threadActor.createValueGrip(desc.value); + descForm.value = createValueGrip(desc.value, + this.registeredPool, this.threadActor.objectGrip); descForm.writable = desc.writable; } else { - descForm.get = this.threadActor.createValueGrip(desc.get); - descForm.set = this.threadActor.createValueGrip(desc.set); + descForm.get = createValueGrip(desc.get || undefined, + this.registeredPool, this.threadActor.objectGrip); + descForm.set = createValueGrip(desc.set || undefined, + this.registeredPool, this.threadActor.objectGrip); } bindings.variables[name] = descForm; } @@ -3583,7 +3634,7 @@ EnvironmentActor.prototype = { * @param aRequest object * The protocol request object. */ - onAssign: function EA_onAssign(aRequest) { + onAssign: function (aRequest) { // TODO: enable the commented-out part when getVariableDescriptor lands // (bug 725815). /*let desc = this.obj.getVariableDescriptor(aRequest.name); @@ -3596,10 +3647,14 @@ EnvironmentActor.prototype = { try { this.obj.setVariable(aRequest.name, aRequest.value); - } catch (e if e instanceof Debugger.DebuggeeWouldRun) { + } catch (e) { + if (e instanceof Debugger.DebuggeeWouldRun) { return { error: "threadWouldRun", cause: e.cause ? e.cause : "setter", message: "Assigning a value would cause the debuggee to run" }; + } else { + throw e; + } } return { from: this.actorID }; }, @@ -3611,7 +3666,7 @@ EnvironmentActor.prototype = { * @param aRequest object * The protocol request object. */ - onBindings: function EA_onBindings(aRequest) { + onBindings: function (aRequest) { return { from: this.actorID, bindings: this._bindings() }; } @@ -3622,41 +3677,56 @@ EnvironmentActor.prototype.requestTypes = { "bindings": EnvironmentActor.prototype.onBindings }; -/** - * Override the toString method in order to get more meaningful script output - * for debugging the debugger. - */ -Debugger.Script.prototype.toString = function() { - let output = ""; - if (this.url) { - output += this.url; - } - if (typeof this.startLine != "undefined") { - output += ":" + this.startLine; - if (this.lineCount && this.lineCount > 1) { - output += "-" + (this.startLine + this.lineCount - 1); - } - } - if (this.strictMode) { - output += ":strict"; - } - return output; -}; +exports.EnvironmentActor = EnvironmentActor; -/** - * Helper property for quickly getting to the line number a stack frame is - * currently paused at. - */ -Object.defineProperty(Debugger.Frame.prototype, "line", { - configurable: true, - get: function() { - if (this.script) { - return this.script.getOffsetLine(this.offset); - } else { - return null; +function hackDebugger(Debugger) { + // TODO: Improve native code instead of hacking on top of it + + /** + * Override the toString method in order to get more meaningful script output + * for debugging the debugger. + */ + Debugger.Script.prototype.toString = function() { + let output = ""; + if (this.url) { + output += this.url; } - } -}); + if (typeof this.staticLevel != "undefined") { + output += ":L" + this.staticLevel; + } + if (typeof this.startLine != "undefined") { + output += ":" + this.startLine; + if (this.lineCount && this.lineCount > 1) { + output += "-" + (this.startLine + this.lineCount - 1); + } + } + if (typeof this.startLine != "undefined") { + output += ":" + this.startLine; + if (this.lineCount && this.lineCount > 1) { + output += "-" + (this.startLine + this.lineCount - 1); + } + } + if (this.strictMode) { + output += ":strict"; + } + return output; + }; + + /** + * Helper property for quickly getting to the line number a stack frame is + * currently paused at. + */ + Object.defineProperty(Debugger.Frame.prototype, "line", { + configurable: true, + get: function() { + if (this.script) { + return this.script.getOffsetLine(this.offset); + } else { + return null; + } + } + }); +} /** @@ -3668,15 +3738,13 @@ Object.defineProperty(Debugger.Frame.prototype, "line", { * is associated. (Currently unused, but required to make this * constructor usable with addGlobalActor.) * - * @param aHooks object - * An object with preNest and postNest methods for calling when entering - * and exiting a nested event loop and also addToParentPool and - * removeFromParentPool methods for handling the lifetime of actors that - * will outlive the thread, like breakpoints. + * @param aParent object + * This actor's parent actor. See ThreadActor for a list of expected + * properties. */ -function ChromeDebuggerActor(aConnection, aHooks) +function ChromeDebuggerActor(aConnection, aParent) { - ThreadActor.call(this, aHooks); + ThreadActor.call(this, aParent); } ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype); @@ -3685,610 +3753,41 @@ update(ChromeDebuggerActor.prototype, { constructor: ChromeDebuggerActor, // A constant prefix that will be used to form the actor ID by the server. - actorPrefix: "chromeDebugger", - - /** - * Override the eligibility check for scripts and sources to make sure every - * script and source with a URL is stored when debugging chrome. - */ - _allowSource: function(aSourceURL) !!aSourceURL, - - /** - * An object that will be used by ThreadActors to tailor their behavior - * depending on the debugging context being required (chrome or content). - * The methods that this object provides must be bound to the ThreadActor - * before use. - */ - globalManager: { - findGlobals: function CDA_findGlobals() { - // Add every global known to the debugger as debuggee. - this.dbg.addAllGlobalsAsDebuggees(); - }, - - /** - * A function that the engine calls when a new global object has been - * created. - * - * @param aGlobal Debugger.Object - * The new global object that was created. - */ - onNewGlobal: function CDA_onNewGlobal(aGlobal) { - this.addDebuggee(aGlobal); - // Notify the client. - this.conn.send({ - from: this.actorID, - type: "newGlobal", - // TODO: after bug 801084 lands see if we need to JSONify this. - hostAnnotations: aGlobal.hostAnnotations - }); - } - } + actorPrefix: "chromeDebugger" }); +exports.ChromeDebuggerActor = ChromeDebuggerActor; /** - * Manages the sources for a thread. Handles source maps, locations in the - * sources, etc for ThreadActors. + * Creates an actor for handling add-on debugging. AddonThreadActor is + * a thin wrapper over ThreadActor. + * + * @param aConnection object + * The DebuggerServerConnection with which this AddonThreadActor + * is associated. (Currently unused, but required to make this + * constructor usable with addGlobalActor.) + * + * @param aParent object + * This actor's parent actor. See ThreadActor for a list of expected + * properties. */ -function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate, - aOnNewSource) { - this._thread = aThreadActor; - this._useSourceMaps = aUseSourceMaps; - this._allow = aAllowPredicate; - this._onNewSource = aOnNewSource; - - // generated source url --> promise of SourceMapConsumer - this._sourceMapsByGeneratedSource = Object.create(null); - // original source url --> promise of SourceMapConsumer - this._sourceMapsByOriginalSource = Object.create(null); - // source url --> SourceActor - this._sourceActors = Object.create(null); - // original url --> generated url - this._generatedUrlsByOriginalUrl = Object.create(null); +function AddonThreadActor(aConnect, aParent) { + ThreadActor.call(this, aParent); } -/** - * Must be a class property because it needs to persist across reloads, same as - * the breakpoint store. - */ -ThreadSources._blackBoxedSources = new Set(["self-hosted"]); -ThreadSources._prettyPrintedSources = new Map(); +AddonThreadActor.prototype = Object.create(ThreadActor.prototype); -ThreadSources.prototype = { - /** - * Return the source actor representing |url|, creating one if none - * exists already. Returns null if |url| is not allowed by the 'allow' - * predicate. - * - * Right now this takes a URL, but in the future it should - * take a Debugger.Source. See bug 637572. - * - * @param String url - * The source URL. - * @param optional SourceMapConsumer sourceMap - * The source map that introduced this source, if any. - * @param optional String generatedSource - * The generated source url that introduced this source via source map, - * if any. - * @param optional String text - * The text content of the source, if immediately available. - * @param optional String contentType - * The content type of the source, if immediately available. - * @returns a SourceActor representing the source at aURL or null. - */ - source: function TS_source({ url, sourceMap, generatedSource, text, - contentType }) { - if (!this._allow(url)) { - return null; - } +update(AddonThreadActor.prototype, { + constructor: AddonThreadActor, - if (url in this._sourceActors) { - return this._sourceActors[url]; - } + // A constant prefix that will be used to form the actor ID by the server. + actorPrefix: "addonThread" +}); - let actor = new SourceActor({ - url: url, - thread: this._thread, - sourceMap: sourceMap, - generatedSource: generatedSource, - text: text, - contentType: contentType - }); - this._thread.threadLifetimePool.addActor(actor); - this._sourceActors[url] = actor; - try { - this._onNewSource(actor); - } catch (e) { - reportError(e); - } - return actor; - }, - - /** - * Only to be used when we aren't source mapping. - */ - _sourceForScript: function TS__sourceForScript(aScript) { - const spec = { - url: aScript.url - }; - - // XXX bug 915433: We can't rely on Debugger.Source.prototype.text if the - // source is an HTML-embedded