├── .gitattributes
├── app
├── images
│ ├── close.png
│ ├── icon-16.png
│ ├── icon-19.png
│ ├── icon-38.png
│ ├── icon-48.png
│ ├── icon-128.png
│ ├── arrows
│ │ ├── arrow-down.png
│ │ ├── arrow-circle.png
│ │ ├── arrow-right.png
│ │ ├── arrow-down-white.png
│ │ ├── arrow-right-white.png
│ │ └── arrow-circle-hover.png
│ └── treeoutlineTriangles.svg
├── html
│ ├── devtools
│ │ └── index.html
│ └── popup
│ │ └── index.html
├── styles
│ └── less
│ │ ├── modules
│ │ ├── JSONView.less
│ │ ├── FrameSelect.less
│ │ ├── XMLView.less
│ │ ├── ControllerDetailView.less
│ │ ├── TabBar.less
│ │ ├── DataView.less
│ │ ├── ElementsRegistry.less
│ │ ├── SplitContainer.less
│ │ ├── ControlTree.less
│ │ ├── DataGrid.less
│ │ └── ODataView.less
│ │ ├── themes
│ │ ├── base
│ │ │ ├── devtools.less
│ │ │ ├── base.less
│ │ │ ├── reset.less
│ │ │ ├── variables.less
│ │ │ ├── globals.less
│ │ │ └── colors.less
│ │ ├── light
│ │ │ └── light.less
│ │ └── dark
│ │ │ └── dark.less
│ │ └── popup.less
├── scripts
│ ├── modules
│ │ ├── injected
│ │ │ ├── rightClickHandler.ts
│ │ │ ├── ui5inspector.js
│ │ │ ├── message.js
│ │ │ └── applicationUtils.js
│ │ ├── background
│ │ │ ├── pageAction.js
│ │ │ └── ContextMenu.js
│ │ ├── utils
│ │ │ ├── ODataNode.js
│ │ │ ├── utils.js
│ │ │ └── EntriesLog.js
│ │ ├── ui
│ │ │ ├── JSONFormatter.js
│ │ │ ├── ODataDetailView.js
│ │ │ ├── FrameSelect.js
│ │ │ ├── XMLDetailView.js
│ │ │ ├── TabBar.js
│ │ │ ├── ControllerDetailView.js
│ │ │ ├── ODataMasterView.js
│ │ │ └── datagrid
│ │ │ │ └── DOMExtension.js
│ │ └── content
│ │ │ └── highLighter.ts
│ ├── content
│ │ ├── detectUI5.js
│ │ └── main.js
│ ├── devtools
│ │ └── panel
│ │ │ └── initialize.js
│ ├── popup
│ │ └── popup.ts
│ ├── injected
│ │ └── detectUI5.js
│ └── background
│ │ └── main.js
├── manifest.json
└── vendor
│ ├── theme-vibrant_ink.js
│ ├── theme-chrome.js
│ └── mode-json.js
├── commitlint.config.js
├── global.d.ts
├── .husky
├── commit-msg
├── pre-commit
└── common.sh
├── .github
├── actions
│ └── bump-version
│ │ ├── action.yml
│ │ └── index.js
└── workflows
│ ├── ci.yml
│ └── release.yml
├── karma.bootstrap.js
├── .gitignore
├── grunt
├── coveralls.js
├── eslint.js
├── clean.js
├── connect.js
├── karma.js
├── browserify.js
├── replace.js
├── usebanner.js
├── jscs.js
├── jshint.js
├── compress.js
├── aliases.js
├── copy.js
├── watch.js
└── less.js
├── tsconfig.json
├── .editorconfig
├── docs
├── bugreport_template.txt
└── guidelines.md
├── .releaserc.json
├── Gruntfile.js
├── scripts
└── update-version.js
├── tests
└── modules
│ ├── ui
│ ├── JSONFormatter.spec.js
│ └── TabBar.spec.js
│ ├── background
│ ├── pageAction.spec.js
│ └── ContextMenu.spec.js
│ ├── injected
│ ├── rightClickHandler.spec.js
│ ├── ui5inspector.spec.js
│ └── message.spec.js
│ ├── content
│ └── highLighter.spec.js
│ └── utils
│ └── utils.spec.js
├── pom.xml
├── .eslintrc
├── .reuse
└── dep5
├── README.md
├── package.json
├── karma.conf.js
└── .jshintrc
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.* -lf
2 | text eol=lf
3 |
--------------------------------------------------------------------------------
/app/images/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/close.png
--------------------------------------------------------------------------------
/app/images/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/icon-16.png
--------------------------------------------------------------------------------
/app/images/icon-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/icon-19.png
--------------------------------------------------------------------------------
/app/images/icon-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/icon-38.png
--------------------------------------------------------------------------------
/app/images/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/icon-48.png
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/app/html/devtools/index.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/images/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/icon-128.png
--------------------------------------------------------------------------------
/app/images/arrows/arrow-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-down.png
--------------------------------------------------------------------------------
/app/images/arrows/arrow-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-circle.png
--------------------------------------------------------------------------------
/app/images/arrows/arrow-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-right.png
--------------------------------------------------------------------------------
/app/images/arrows/arrow-down-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-down-white.png
--------------------------------------------------------------------------------
/app/images/arrows/arrow-right-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-right-white.png
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | declare global {
4 |
5 | interface Window {
6 | chrome: any;
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 | . "$(dirname "$0")/common.sh"
4 |
5 | npm run husky:commit-msg
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 | . "$(dirname "$0")/common.sh"
4 |
5 | npm run husky:pre-commit
--------------------------------------------------------------------------------
/app/images/arrows/arrow-circle-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UI5/inspector/HEAD/app/images/arrows/arrow-circle-hover.png
--------------------------------------------------------------------------------
/app/images/treeoutlineTriangles.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/actions/bump-version/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Bump version'
2 | description: 'Bump version in files'
3 | runs:
4 | using: 'node16'
5 | main: 'index.js'
6 |
--------------------------------------------------------------------------------
/.husky/common.sh:
--------------------------------------------------------------------------------
1 | command_exists () {
2 | command -v "$1" >/dev/null 2>&1
3 | }
4 |
5 | # Windows 10, Git Bash and Yarn workaround
6 | if command_exists winpty && test -t 1; then
7 | exec < /dev/tty
8 | fi
--------------------------------------------------------------------------------
/app/styles/less/modules/JSONView.less:
--------------------------------------------------------------------------------
1 | [json] {
2 | key {
3 | color: @purple;
4 | }
5 |
6 | string {
7 | color: @gray-darkest;
8 | }
9 |
10 | number {
11 | color: @gray-darkest;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/karma.bootstrap.js:
--------------------------------------------------------------------------------
1 | /*global document, window*/
2 | (function (window) {
3 | 'use strict';
4 |
5 | document.body.innerHTML += '
';
6 |
7 | window.assert = chai.assert;
8 | window.expect = chai.expect;
9 | window.should = chai.should();
10 | })(window);
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .idea
3 |
4 | # NodeJS
5 | node_modules
6 | npm-debug.log
7 |
8 | # Dependencies
9 |
10 |
11 | # Temporary folders
12 | temp
13 | .tmp
14 | tests/reports
15 | tempDist
16 |
17 | # App files
18 | **/styles/*.css
19 | FRONTEND.md
20 |
21 | # Distribution folders
22 | dist
23 | package
24 |
--------------------------------------------------------------------------------
/grunt/coveralls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Upload lcov file to coveralls.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | target: {
11 | src: '<%= tests %>/reports/coverage/lcov.info'
12 | }
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "app/**/*",
4 | "global.d.ts"
5 | ],
6 | "compilerOptions": {
7 | "declaration": true,
8 | "outDir": "tempDist",
9 | "target": "ES2021",
10 | "skipLibCheck": true,
11 | "sourceMap": true,
12 | "moduleResolution": "node",
13 | "allowJs": true,
14 | "checkJs": false
15 | }
16 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | indent_style = space
10 | indent_size = 4
11 | end_of_line = lf
12 | charset = utf-8
13 | trim_trailing_whitespace = true
14 | insert_final_newline = true
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/grunt/eslint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Make sure code styles are up to par and there are no obvious mistakes.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | configFile: '.eslintrc',
12 | },
13 | target: ['<%= app %>/scripts/**/*.js'],
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/app/styles/less/modules/FrameSelect.less:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | FrameSelect
3 | ========================================================================== */
4 |
5 | frame-select {
6 | padding-top: 1px;
7 | padding-bottom: 1px;
8 | padding-left: 10px;
9 | }
10 |
11 | frame-select select {
12 | outline: 2px solid @lighter-blue;
13 | }
14 |
--------------------------------------------------------------------------------
/docs/bugreport_template.txt:
--------------------------------------------------------------------------------
1 | OpenUI5/SAPUI5 version:
2 |
3 | Browser/version:
4 |
5 | URL (minimal example if possible):
6 |
7 | User/password (if required and possible - do not post any confidential information here):
8 |
9 | Steps to reproduce the problem:
10 | 1.
11 | 2.
12 | 3.
13 |
14 | What is the expected result?
15 |
16 | What happens instead?
17 |
18 | Any other information? (attach screenshots if possible)
19 |
20 |
--------------------------------------------------------------------------------
/grunt/clean.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Empties folders to start fresh.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | all: {
11 | files: [{
12 | dot: true,
13 | src: [
14 | '<%= dist %>/*'
15 | ]
16 | }]
17 | }
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/grunt/connect.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Create Grunt server.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | hostname: 'localhost'
12 | },
13 | dist: {
14 | options: {
15 | open: true,
16 | port: 9001,
17 | base: ['<%= dist %>']
18 | }
19 | }
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/app/styles/less/modules/XMLView.less:
--------------------------------------------------------------------------------
1 | xml-view {
2 | height: 100%;
3 | }
4 |
5 | #elements-registry-control-xmlview {
6 | .editorAlt {
7 | display: flex;
8 | div {
9 | margin: auto;
10 | cursor: pointer;
11 | }
12 | }
13 |
14 | div.hidden {
15 | visibility: hidden;
16 | }
17 |
18 | .editorAlt, #xmlEditor {
19 | position: absolute;
20 | top: 25px;
21 | left: 0;
22 | right: 0;
23 | bottom: 0;
24 | height: auto;
25 | }
26 | }
--------------------------------------------------------------------------------
/app/styles/less/themes/base/devtools.less:
--------------------------------------------------------------------------------
1 | overlay {
2 | background: @tab-background-color;
3 | height: 100%;
4 | position: fixed;
5 | left: 0;
6 | top: 18px;
7 | width: 100%;
8 | z-index: 9999999;
9 |
10 | & > section {
11 | display: none;
12 | }
13 |
14 | h1 {
15 | background: @bg-gray-lightest;
16 | padding: 2rem 1rem;
17 | line-height: 1.25;
18 | }
19 |
20 | p {
21 | padding: 0 1rem;
22 | margin: 1rem 0;
23 | line-height: 1.25;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/grunt/karma.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Unit test runner.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | configFile: 'karma.conf.js',
12 | client: {
13 | mocha: {
14 | reporter: 'html',
15 | ui: 'bdd'
16 | }
17 | }
18 | },
19 | dev: {},
20 | CI: {
21 | singleRun: true
22 | }
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/grunt/browserify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Bundl the needed modules.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | debug: true
12 | },
13 | dev: {
14 | files: [{
15 | expand: true,
16 | cwd: '<%= tempDist %>/scripts',
17 | src: ['**/*.js', '!modules/**/*.js'],
18 | dest: '<%= dist %>/scripts'
19 | }]
20 | }
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/grunt/replace.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Replace text in files.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | toolsAPI: {
11 | src: [
12 | '<%= app %>/vendor/ToolsAPI.js'
13 | ],
14 | dest: '<%= dist %>/vendor/ToolsAPI.js',
15 | replacements: [{
16 | from: 'sap.ui.define([',
17 | to: 'sap.ui.define(\'ToolsAPI\', ['
18 | }]
19 | }
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to automate the creation of the build artifact
2 | name: CI
3 | # Controls when the workflow will run
4 | on:
5 | pull_request:
6 | branches: [ master ]
7 | # Allows you to run this workflow manually from the Actions tab
8 | workflow_dispatch:
9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
10 | jobs:
11 | test:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Install NPM dependencies
16 | run: npm ci
17 | - name: Run tests
18 | run: grunt test
19 |
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@semantic-release/commit-analyzer",
4 | "@semantic-release/release-notes-generator",
5 | "@semantic-release/changelog",
6 | [
7 | "@semantic-release/exec",
8 | {
9 | "prepareCmd": "node ./scripts/update-version.js ${nextRelease.version} && npm i"
10 | }
11 | ],
12 | [
13 | "@semantic-release/github",
14 | {
15 | "assets": [
16 | {
17 | "path": "package/ui5inspector.zip",
18 | "name": "ui5inspector-v${nextRelease.version}.zip"
19 | }
20 | ]
21 | }
22 | ]
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/app/styles/less/themes/base/base.less:
--------------------------------------------------------------------------------
1 | @import "colors.less";
2 | @import "variables.less";
3 | @import "globals.less";
4 | @import "reset.less";
5 |
6 | @import "devtools.less";
7 |
8 | // Components
9 | @import "../../modules/TabBar.less";
10 | @import "../../modules/ControlTree.less";
11 | @import "../../modules/DataView.less";
12 | @import "../../modules/FrameSelect.less";
13 | @import "../../modules/SplitContainer.less";
14 | @import "../../modules/JSONView.less";
15 | @import "../../modules/ODataView.less";
16 | @import "../../modules/ControllerDetailView.less";
17 | @import "../../modules/XMLView.less";
18 | @import "../../modules/DataGrid.less";
19 | @import "../../modules/ElementsRegistry.less";
20 |
21 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * SAP
3 | * (c) Copyright 2015 SAP SE or an SAP affiliate company.
4 | * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5 | */
6 |
7 | 'use strict';
8 |
9 | /**
10 | * Grunt configuration file.
11 | * @param {Object} grunt
12 | */
13 | module.exports = function (grunt) {
14 |
15 | require('time-grunt')(grunt);
16 |
17 | require('load-grunt-config')(grunt, {
18 | // Data passed into config. Can use with <%= *** %>
19 | data: {
20 | app: 'app',
21 | dist: 'dist',
22 | tests: 'tests',
23 | bower: 'bower_components',
24 | grunt: 'grunt',
25 | tempDist: 'tempDist'
26 | }
27 | });
28 |
29 | };
30 |
--------------------------------------------------------------------------------
/grunt/usebanner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Add copy right comments inside the distributed files.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | HTML: {
11 | options: {
12 | position: 'top',
13 | banner: '',
18 | linebreak: true
19 | },
20 | files: {
21 | src: ['<%= dist %>/**/*.html']
22 | }
23 | }
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/grunt/jscs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Make sure code styles are up to par and there are no obvious mistakes.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | config: '.jscsrc',
12 | requireCurlyBraces: ['if']
13 | },
14 | scripts: {
15 | files: {
16 | src: ['<%= app %>/scripts/**/*.js']
17 | }
18 | },
19 | karma: {
20 | files: {
21 | src: ['<%= tests %>/**/*.js', '!<%= tests %>/reports/**/*.*']
22 | }
23 | },
24 | gruntfiles: {
25 | files: {
26 | src: ['Gruntfile.js', '<%= grunt %>/*.js']
27 | }
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/app/styles/less/modules/ControllerDetailView.less:
--------------------------------------------------------------------------------
1 | xml-view {
2 | height: 100%;
3 | }
4 |
5 | #elements-registry-control-controller {
6 | .editorAlt {
7 | display: flex;
8 | div {
9 | cursor: pointer;
10 | }
11 | }
12 |
13 | div.hidden {
14 | display: none !important;
15 | }
16 |
17 | .editorAlt {
18 | position: absolute;
19 | top: .5rem;
20 | left: .5rem;
21 | right: .5rem;
22 | bottom: .5rem;
23 | height: auto;
24 | }
25 |
26 | .firstColAlignment {
27 | text-align: end;
28 | }
29 |
30 | .longTextReduce {
31 | text-overflow: ellipsis;
32 | white-space: nowrap;
33 | overflow: hidden;
34 | }
35 |
36 | #controllerEditor{
37 | display: grid;
38 | grid-template-columns: 25% auto;
39 | gap: .5rem;
40 | padding: .5rem;
41 | }
42 | }
--------------------------------------------------------------------------------
/grunt/jshint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Make sure code styles are up to par and there are no obvious mistakes.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | node: true,
12 | jshintrc: '.jshintrc',
13 | reporter: require('jshint-stylish')
14 | },
15 | scripts: {
16 | files: {
17 | src: ['<%= app %>/scripts/**/*.js']
18 | }
19 | },
20 | karma: {
21 | files: {
22 | src: ['<%= tests %>/**/*.js', '!<%= tests %>/reports/**/*.*']
23 | }
24 | },
25 | gruntfiles: {
26 | files: {
27 | src: ['Gruntfile.js', '<%= grunt %>/*.js']
28 | }
29 | }
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/scripts/update-version.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const args = process.argv.slice(2);
5 | console.log('Updating version - ' + args[0]);
6 |
7 | const packagePath = path.join(path.dirname(__dirname), 'package.json');
8 | const manifestPath = path.join(path.dirname(__dirname), 'app/manifest.json');
9 |
10 | const version = args[0];
11 | const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
12 | const manifestJson = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
13 |
14 | packageJson.version = version;
15 | manifestJson.version = version;
16 |
17 | try {
18 | fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
19 | fs.writeFileSync(manifestPath, JSON.stringify(manifestJson, null, 4));
20 | //file written successfully
21 | console.log('Update successful');
22 | } catch (err) {
23 | console.error(err);
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to automate the creation of the build artifact
2 | name: Release
3 | # Controls when the workflow will run
4 | on:
5 | schedule:
6 | - cron: "0 9 15 * *"
7 | # Allows you to run this workflow manually from the Actions tab
8 | workflow_dispatch:
9 | jobs:
10 | release:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Install NPM dependencies
15 | run: npm ci
16 | - name: Run tests
17 | run: grunt test
18 | # Release using semantic-relaease if there are new smenatic commits
19 | - name: Release
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | run: npx semantic-release
23 | - name: Bump Version
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 | uses: ./.github/actions/bump-version
27 |
--------------------------------------------------------------------------------
/grunt/compress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Compress dist files to zip package.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | dist: {
11 | options: {
12 | /**
13 | * Describe archive location.
14 | * @returns {string}
15 | */
16 | archive: function () {
17 | // If we need to add the version to the zip name
18 | // var manifest = grunt.file.readJSON('app/manifest.json');
19 | return 'package/ui5inspector.zip';
20 | }
21 | },
22 | files: [{
23 | expand: true,
24 | cwd: 'dist/',
25 | src: ['**'],
26 | dest: ''
27 | }]
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/app/scripts/modules/injected/rightClickHandler.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 |
5 | // Reference for the ID of the last click UI5 control.
6 | _clickedElementId: null,
7 |
8 | /**
9 | * Return the ID of the UI5 control that was clicked.
10 | * @returns {string}
11 | */
12 | getClickedElementId: function () {
13 | return this._clickedElementId;
14 | },
15 |
16 | /**
17 | * Set the ID of the UI5 control that was clicked.
18 | * @param {Element} target
19 | * @returns {string}
20 | */
21 | setClickedElementId: function (target) {
22 | while (target && !target.getAttribute('data-sap-ui')) {
23 | if (target.nodeName === 'BODY') {
24 | break;
25 | }
26 | target = target.parentNode;
27 | }
28 |
29 | this._clickedElementId = target.id;
30 | return this;
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/grunt/aliases.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Grunt aliases
5 | * @type {Object}
6 | */
7 | module.exports = {
8 |
9 | // Make sure code styles are up to par and there are no obvious mistakes.
10 | lint: [
11 | 'jshint',
12 | 'eslint'
13 | ],
14 |
15 | // Preprocess .js and .less files.
16 | preprocess: [
17 | 'browserify',
18 | 'less'
19 | ],
20 |
21 | // Builds the project without running any test.
22 | dist: [
23 | 'clean',
24 | 'preprocess',
25 | 'copy',
26 | 'replace',
27 | 'usebanner',
28 | 'compress'
29 | ],
30 |
31 | // Runs all tests for continues integration.
32 | test: [
33 | 'lint',
34 | 'less',
35 | 'karma:CI'
36 | ],
37 |
38 | // Builds the project and monitor changes in files
39 | default: [
40 | 'dist',
41 | 'watch'
42 | ]
43 | };
44 |
45 |
--------------------------------------------------------------------------------
/app/scripts/modules/background/pageAction.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Page action.
5 | * @type {{create: Function}}
6 | */
7 | var pageAction = {
8 |
9 | /**
10 | * Create page action.
11 | * @param {Object} options
12 | */
13 | create: function (options) {
14 | var framework = options.framework;
15 | var version = options.version;
16 | var tabId = options.tabId;
17 |
18 | chrome.action.setTitle({
19 | tabId: tabId,
20 | title: 'This page is using ' + framework + ' v' + version
21 | });
22 | },
23 |
24 | /**
25 | * Disable page action.
26 | *
27 | */
28 | disable: function () {
29 | chrome.action.disable();
30 | },
31 |
32 | /**
33 | * Enable page action.
34 | *
35 | */
36 | enable: function () {
37 | chrome.action.enable();
38 | }
39 | };
40 |
41 | module.exports = pageAction;
42 |
--------------------------------------------------------------------------------
/grunt/copy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Copies remaining files.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | dist: {
11 | files: [{
12 | expand: true,
13 | dot: true,
14 | cwd: '<%= app %>',
15 | dest: '<%= dist %>',
16 | src: [
17 | '*.txt',
18 | 'html/**/*.html',
19 | 'images/**/*.*',
20 | 'vendor/*.js',
21 | 'manifest.json'
22 | ]
23 | }]
24 | },
25 |
26 | license: {
27 | files: [{
28 | expand: true,
29 | dot: true,
30 | cwd: './',
31 | dest: '<%= dist %>',
32 | src: [
33 | '*.txt'
34 | ]
35 | }]
36 | }
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/app/styles/less/themes/base/reset.less:
--------------------------------------------------------------------------------
1 | //
2 | // CSS reset
3 | // --------------------------------------------------
4 |
5 | * {
6 | box-sizing: border-box;
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | html {
12 | display: flex;
13 | height: 100%;
14 | overflow: hidden;
15 | }
16 |
17 | body {
18 | color: @text-color;
19 | background-color: @tab-background-color;
20 | display: flex;
21 | flex-direction: column;
22 | font-family: @font-family-windows;
23 | font-size: @main-font-size-windows;
24 | height: 100%;
25 | width: 100%;
26 | line-height: @line-height;
27 | flex-grow: 1;
28 | }
29 |
30 | body[os='linux'] {
31 | font-family: @font-family-linux;
32 | font-size: @main-font-size-linux;
33 | }
34 |
35 | body[os='mac'] {
36 | font-family: @font-family-mac;
37 | font-size: @main-font-size-mac;
38 | }
39 |
40 | ol, ul {
41 | list-style: none;
42 | }
43 |
44 | label {
45 | cursor: pointer;
46 | -webkit-user-select: none;
47 | }
48 |
--------------------------------------------------------------------------------
/app/scripts/modules/utils/ODataNode.js:
--------------------------------------------------------------------------------
1 | /* globals createElementWithClass */
2 |
3 | const DataGrid = require('../ui/datagrid/DataGrid.js');
4 |
5 | const BATCH_ICON = 128194;
6 | const REQUEST_ICON = 128463;
7 |
8 | class ODataNode extends DataGrid.SortableDataGridNode {
9 |
10 | createCell(columnId) {
11 | const cell = super.createCell(columnId);
12 | if (columnId === 'name') {
13 | this._renderPrimaryCell(cell, columnId);
14 | }
15 |
16 | return cell;
17 | }
18 |
19 | _renderPrimaryCell(cell) {
20 | const iconElement = createElementWithClass('span', 'icon');
21 | const iSymbol = (this.data.isBatch) ? BATCH_ICON : REQUEST_ICON;
22 | const sClass = (this.data.isBatch) ? 'batchIcon' : 'requestIcon';
23 |
24 | iconElement.classList.add(sClass);
25 | iconElement.innerHTML = `${iSymbol}`;
26 | cell.prepend(iconElement);
27 |
28 | cell.title = this.data.url;
29 | }
30 | }
31 |
32 | module.exports = ODataNode;
33 |
--------------------------------------------------------------------------------
/app/scripts/modules/injected/ui5inspector.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Create global reference for the extension.
5 | * @private
6 | */
7 | function _createReferences() {
8 | if (window.ui5inspector === undefined) {
9 | window.ui5inspector = {
10 | events: Object.create(null)
11 | };
12 | }
13 | }
14 |
15 | /**
16 | * Register event listener if is not already registered.
17 | * @param {string} eventName - the name of the event that will be register
18 | * @callback
19 | * @private
20 | */
21 | function _registerEventListener(eventName, callback) {
22 | if (window.ui5inspector.events[eventName] === undefined) {
23 | // Register reference
24 | window.ui5inspector.events[eventName] = {
25 | callback: callback.name,
26 | state: 'registered'
27 | };
28 |
29 | document.addEventListener(eventName, callback, false);
30 | }
31 | }
32 |
33 | module.exports = {
34 | createReferences: _createReferences,
35 | registerEventListener: _registerEventListener
36 | };
37 |
--------------------------------------------------------------------------------
/grunt/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Watches files for changes and runs tasks based on the changed files.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | options: {
11 | spawn: false,
12 | interval: 5000
13 | },
14 | html: {
15 | files: ['<%= app %>/**/*.html'],
16 | tasks: ['copy']
17 | },
18 | js: {
19 | files: ['<%= app %>/scripts/**/*.js'],
20 | tasks: ['newer:jshint:scripts', 'newer:eslint:scripts', 'browserify']
21 | },
22 | tests: {
23 | files: ['<%= tests %>/**/*.js'],
24 | tasks: ['newer:jshint:karma', 'newer:eslint:karma']
25 | },
26 | gruntfiles: {
27 | files: ['Gruntfile.js', '<%= grunt %>/*.js'],
28 | tasks: ['newer:jshint:gruntfiles', 'newer:eslint:gruntfiles']
29 | },
30 | styles: {
31 | files: ['<%= app %>/styles/less/**/*.less'],
32 | tasks: ['less']
33 | }
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/app/html/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | What's new
13 | Demo Kit (Documentation)
14 | http://openui5.org
15 |
16 |
17 | What's new
18 | Demo Kit (Documentation)
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/grunt/less.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Compiles less files to css files.
5 | * @param {Object} grunt
6 | * @param {Object} config
7 | */
8 | module.exports = function (grunt, config) {
9 | return {
10 | dist: {
11 | options: {
12 | optimization: 2
13 | },
14 | files: [{
15 | expand: true,
16 | cwd: '<%= app %>/styles/less',
17 | dest: '<%= dist %>/styles',
18 | src: [
19 | 'themes/light/light.less',
20 | 'themes/dark/dark.less',
21 | 'popup.less'
22 | ],
23 | ext: '.css'
24 | }]
25 | },
26 | test: {
27 | files: [{
28 | expand: true,
29 | cwd: '<%= app %>/styles/less',
30 | dest: '<%= tests %>/styles',
31 | src: [
32 | 'themes/light/light.less',
33 | 'themes/dark/dark.less'
34 | ],
35 | ext: '.css'
36 | }]
37 | }
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/app/styles/less/themes/light/light.less:
--------------------------------------------------------------------------------
1 | @import "../base/base.less";
2 |
3 | // Colors
4 | @black: #000;
5 |
6 | @gray-darkest: #222;
7 | @gray-darker: #333;
8 | @gray-dark: #a3a3a3;
9 | @gray: #bbb;
10 | @grey-light: #c0c0c0;
11 | @gray-lighter: #d4d4d4;
12 | @gray-lightest: #eee;
13 |
14 | @white: #fff;
15 |
16 | @purple: #881280;
17 | @blue: #1a1aa6;
18 | @light-blue: #0000ee;
19 | @lighter-blue: #3879D9;
20 | @red: #994500;
21 | @red-saturated: #C80000;
22 |
23 | @highlight-color: #ffff70;
24 |
25 | @hover-color: rgba(56, 121, 217, 0.1);
26 | @selected-element-color: @white;
27 | @section-title-color: @black;
28 | @tab-background-color: @white;
29 | @search-background-color: @white;
30 | @odata-tab-background-color: @white;
31 | @bg-datagrid-row-background-color: #f3f3f3;
32 | @bg-datagrid-header-background-color: #f2f2f2;
33 | @bg-datagrid-th-sortable-background-color: #e6e6e6;
34 | @bg-datagrid-tr-hover-background-color: AliceBlue;
35 |
--------------------------------------------------------------------------------
/app/styles/less/themes/base/variables.less:
--------------------------------------------------------------------------------
1 | // Font
2 | @font-family-windows: Consolas, Lucida Console, monospace;
3 | @font-family-mac: Menlo, monospace;
4 | @font-family-linux: dejavu sans mono, monospace;
5 |
6 | @tabs-font-family-windows: 'Segoe UI', Tahoma, sans-serif;
7 | @tabs-font-family-linux: Ubuntu, Arial, sans-serif;
8 | @tabs-font-family-mac: 'Lucida Grande', sans-serif;
9 |
10 | @main-font-size-windows: 12px;
11 | @main-font-size-mac: 11px;
12 | @main-font-size-linux: 11px;
13 |
14 | @font-size-large: ceil((@font-size * 1.25)); // 15px
15 | @font-size: 12px;
16 | @font-size-small: ceil((@font-size * 0.85)); // ~10px
17 |
18 | // Line height
19 | @line-height-large: 1.3333333;
20 | @line-height: 14px;
21 | @line-height-small: 1.5;
22 |
23 | // Padding
24 | @padding-base-vertical: 6px;
25 | @padding-base-horizontal: 12px;
26 | @padding-large-vertical: 10px;
27 | @padding-large-horizontal: 16px;
28 | @padding-small-vertical: 5px;
29 | @padding-small-horizontal: 10px;
30 | @padding-xs-vertical: 1px;
31 | @padding-xs-horizontal: 5px;
32 |
33 |
--------------------------------------------------------------------------------
/app/styles/less/themes/dark/dark.less:
--------------------------------------------------------------------------------
1 | @import "../base/base.less";
2 |
3 | // Colors
4 | @black: #000;
5 |
6 | @gray-darkest: #949494;
7 | @gray-darker: #333;
8 | @gray-dark: #a3a3a3;
9 | @gray: #626262;
10 | @grey-light: #c0c0c0;
11 | @gray-lighter: #d4d4d4;
12 | @gray-lightest: @black;
13 |
14 | @white: #fff;
15 |
16 | @purple: #59AFD6;
17 | @blue: #ED9868;
18 | @light-blue: #ED9868;
19 | @lighter-blue: #C98730;
20 | @red: #9ABADA;
21 | @red-saturated: #22D4C2;
22 |
23 | @highlight-color: rgba(98, 98, 98, 0.9);
24 |
25 | @hover-color: rgba(201, 135, 48, 0.1);
26 | @selected-element-color: @black;
27 | @section-title-color: #CBCBCB;
28 | @tab-background-color: #252525;
29 | @search-background-color: #1B1B1B;
30 | @odata-tab-background-color: #252525;
31 | @bg-datagrid-row-background-color: @black;
32 | @bg-datagrid-header-background-color: @black;
33 | @bg-datagrid-th-sortable-background-color: #252525;
34 | @bg-datagrid-tr-hover-background-color: rgba(201, 135, 48, 0.1);
35 |
--------------------------------------------------------------------------------
/app/scripts/content/detectUI5.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | var utils = require('../modules/utils/utils.js');
5 |
6 | // Create a port with background page for continuous message communication
7 | var port = utils.getPort();
8 |
9 | // Inject a script file in the current page
10 | var script = document.createElement('script');
11 | script.src = chrome.runtime.getURL('/scripts/injected/detectUI5.js');
12 | document.head.appendChild(script);
13 |
14 | /**
15 | * Delete the injected file, when it is loaded.
16 | */
17 | script.onload = function () {
18 | script.parentNode.removeChild(script);
19 | };
20 |
21 | // Listen for messages from the background page
22 | port.onMessage(function (message) {
23 | // Resolve incoming messages
24 | if (message.action === 'do-ui5-detection') {
25 | document.dispatchEvent(new Event('do-ui5-detection-injected'));
26 | }
27 | });
28 |
29 | /**
30 | * Listens for messages from the injected script.
31 | */
32 | document.addEventListener('detect-ui5-content', function sendEvent(detectEvent) {
33 | // Send the received event detail object to background page
34 | port.postMessage(detectEvent.detail);
35 |
36 | }, false);
37 | }());
38 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/JSONFormatter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Sample usage
5 | * JSONView = require('../../../modules/ui/JSONFormatter.js');
6 | * JSONViewFormater.formatJSONtoHTML(sampleJSONData);
7 | */
8 |
9 | /**
10 | *
11 | * @param {Object} json
12 | * @returns {string|HTML}
13 | * @private
14 | */
15 | function _syntaxHighlight(json) {
16 | json = JSON.stringify(json, undefined, 2);
17 | json = json.replace(/&/g, '&').replace(//g, '>');
18 | return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
19 | var tagName = 'number';
20 | if (/^"/.test(match)) {
21 | if (/:$/.test(match)) {
22 | tagName = 'key';
23 | } else {
24 | tagName = 'string';
25 | }
26 | } else if (/true|false/.test(match)) {
27 | tagName = 'boolean';
28 | } else if (/null/.test(match)) {
29 | tagName = 'null';
30 | }
31 | return '<' + tagName + '>' + match + '' + tagName + '>';
32 | });
33 | }
34 |
35 | module.exports = {
36 |
37 | /**
38 | * Create HTML from a json object.
39 | * @param {Object} json
40 | * @returns {string}
41 | */
42 | formatJSONtoHTML: function (json) {
43 | return '' + _syntaxHighlight(json) + '
';
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/tests/modules/ui/JSONFormatter.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var JSONFormatter = require('../../../app/scripts/modules/ui/JSONFormatter.js');
4 |
5 | var mockData = {
6 | title: 'Example Schema',
7 | type: 'object',
8 | properties: {
9 | firstName: {
10 | type: 'string'
11 | },
12 | lastName: {
13 | type: 'string'
14 | },
15 | age: {
16 | description: 'Age in years',
17 | type: 'integer',
18 | minimum: 0
19 | }
20 | },
21 | required: ['firstName', 'lastName'],
22 | boolean: true,
23 | null: null
24 | };
25 |
26 | describe('JSONFormatter', function () {
27 | var fixtures = document.getElementById('fixtures');
28 |
29 | beforeEach(function () {
30 | // Create HTML elements from mockData object
31 | fixtures.innerHTML = JSONFormatter.formatJSONtoHTML(mockData);
32 | });
33 | afterEach(function () {
34 | fixtures.innerHTML = '';
35 | });
36 |
37 | it('should create HTML elements from JSON object', function () {
38 | // Get all created elements from JSONFormater
39 | var result = JSON.parse(fixtures.firstChild.innerText);
40 |
41 | // Stringify the result, so that it can be compared to the mockData
42 | result = JSON.stringify(result);
43 |
44 | // Compaction
45 | result.should.equal(JSON.stringify(mockData));
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/.github/actions/bump-version/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const core = require('@actions/core');
3 | const github = require('@actions/github');
4 | const exec = require('@actions/exec');
5 |
6 | const run = async () => {
7 | core.info('Setup octokit');
8 | const octokit = github.getOctokit(process.env.GITHUB_TOKEN);
9 | const { owner, repo } = github.context.repo;
10 |
11 | core.info('Fetch latest release info');
12 | const latestRelease = await octokit.rest.repos.getLatestRelease({
13 | owner,
14 | repo,
15 | });
16 |
17 | core.info('Set variables');
18 | const version = latestRelease.data.tag_name.slice(1);
19 |
20 | core.info('Update files version field');
21 | await exec.exec('node', [path.join(process.env.GITHUB_WORKSPACE, './scripts/update-version.js'), version]);
22 |
23 | await exec.exec('git', ['config', '--global', 'user.name', 'github-actions']);
24 | await exec.exec('git', ['config', '--global', 'user.email', 'actions@users.noreply.github.com']);
25 | await exec.exec('git', ['checkout', '-b', `bump-version-${version}`]);
26 | await exec.exec('git', ['commit', '-am', `chore: release ${version}`]);
27 | await exec.exec('git', ['push', '-u', 'origin', `bump-version-${version}`]);
28 | await exec.exec('gh', ['pr', 'create', '--fill']);
29 | };
30 |
31 | run()
32 | .then(() => core.info('Updated files version successfully'))
33 | .catch(error => core.setFailed(error.message));
34 |
--------------------------------------------------------------------------------
/app/styles/less/themes/base/globals.less:
--------------------------------------------------------------------------------
1 | //
2 | // Global styles
3 | // --------------------------------------------------
4 |
5 | arrow,
6 | place-holder {
7 | display: block;
8 | height: @line-height;
9 | width: @font-size;
10 | }
11 |
12 | arrow {
13 | background-repeat: no-repeat;
14 | background-position: center center;
15 |
16 | &[right] {
17 | background-image: url("/images/arrows/arrow-right.png");
18 | background-size: 7px 8px;
19 | }
20 | &[down] {
21 | background-image: url("/images/arrows/arrow-down.png");
22 | background-size: 8px 7px;
23 | }
24 | }
25 |
26 | arrow {
27 | cursor: pointer;
28 | }
29 |
30 | attribute,
31 | string {
32 | color: @red;
33 | }
34 |
35 | attribute-value,
36 | boolean {
37 | color: @blue;
38 | }
39 |
40 | key {
41 | color: @red-saturated;
42 | }
43 |
44 | no-data {
45 | color: @grey-light;
46 | display: block;
47 | padding-top: @padding-base-horizontal;
48 | text-align: center;
49 | }
50 |
51 | pre {
52 | display: inline;
53 | word-wrap: break-word;
54 | white-space: pre-wrap;
55 | }
56 |
57 | [verical-aligment] {
58 | display: inline-block;
59 | vertical-align: middle;
60 | }
61 |
62 | section-title, value {
63 | color: @title-color;
64 | }
65 |
66 | tag {
67 | color: @purple;
68 | }
69 |
70 | [hidden] {
71 | display: none;
72 | }
73 |
74 | opaque {
75 | color: @grey-light;
76 | }
77 |
78 | [padding] {
79 | padding: 10px;
80 | }
81 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.sap.ui5.inspector
5 | package
6 | 0.9.10
7 | pom
8 |
9 | UI5 Inspector
10 | With the UI5 Inspector, you can easily debug and support your OpenUI5 or SAPUI5-based apps.
11 | https://chrome.google.com/webstore/detail/ui5-inspector/bebecogbafbighhaildooiibipcnbngo
12 |
13 |
14 |
15 | Apache License, Version 2.0
16 | http://www.apache.org/licenses/LICENSE-2.0.txt
17 | repo
18 |
19 |
20 |
21 |
22 | SAP
23 | http://www.sap.com
24 |
25 |
26 |
27 |
28 | UI5
29 | SAP SE
30 | http://www.sap.com
31 |
32 |
33 |
34 |
35 |
36 | scm:git:https://github.com/SAP/ui5-inspector.git
37 |
38 |
39 | https://github.com/SAP/ui5-inspector
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/styles/less/popup.less:
--------------------------------------------------------------------------------
1 | @import "themes/base/variables.less";
2 |
3 | @icon-height: 16px;
4 | @icon-width: 16px;
5 | @icon-offset: 6px;
6 |
7 | @gray-darker: #333;
8 | @popup-link-color: #0000ee;
9 |
10 | [hidden] {
11 | display: none;
12 | }
13 |
14 | popup {
15 | color: @gray-darker;
16 | display: inline-block;
17 | padding: 5px 10px 5px 6px;
18 |
19 | product {
20 | display: block;
21 | height: 16px;
22 | line-height: 16px;
23 | margin-top: -1px;
24 | white-space: nowrap;
25 | }
26 |
27 | library {
28 | display: inline-block;
29 | font-weight: bold;
30 | }
31 |
32 | buildtime {
33 | display: inline-block;
34 | }
35 |
36 | logo {
37 | background-image: url("/images/icon-38.png");
38 | background-repeat: no-repeat;
39 | background-position: left center;
40 | background-size: 16px;
41 | display: inline-block;
42 | height: 16px;
43 | width: @icon-width + @icon-offset;
44 | vertical-align: bottom;
45 | }
46 |
47 | links {
48 | display: block;
49 | padding-left: @icon-width + @icon-offset;
50 | margin-top: 4px;
51 | margin-bottom: 5px;
52 |
53 | a,
54 | a:visited {
55 | color: @popup-link-color;
56 | display: block;
57 | line-height: 1.3;
58 | white-space: nowrap;
59 | }
60 |
61 | a:hover {
62 | text-decoration: none;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/styles/less/themes/base/colors.less:
--------------------------------------------------------------------------------
1 | // Background colors
2 | @bg-black: @black;
3 |
4 | @bg-gray-darkest: @gray-darkest;
5 | @bg-gray-darker: @gray-darker;
6 | @bg-gray-dark: @gray-dark;
7 | @bg-gray: @gray;
8 | @bg-grey-light: @grey-light;
9 | @bg-gray-lighter: @gray-lighter;
10 | @bg-gray-lightest: @gray-lightest;
11 |
12 | @bg-white: @tab-background-color;
13 |
14 | @bg-purple: @purple;
15 | @bg-blue: @blue;
16 | @bg-light-blue: @light-blue;
17 | @bg-lighter-blue: @lighter-blue;
18 | @bg-red: @red;
19 |
20 | @bg-search-field: @search-background-color;
21 | @bg-highlight-color: @highlight-color;
22 | @bg-selected-element-color: @selected-element-color;
23 | @bg-datagrid: @odata-tab-background-color;
24 | @bg-datagrid-row: @bg-datagrid-row-background-color;
25 | @bg-datagrid-header: @bg-datagrid-header-background-color;
26 | @bg-datagrid-th-sortable: @bg-datagrid-th-sortable-background-color;
27 | @bg-datagrid-tr-hover: @bg-datagrid-tr-hover-background-color;
28 |
29 | // Border colors
30 | @border-gray-dark: @gray-dark;
31 | @border-gray: @gray;
32 | @border-grey-light: @grey-light;
33 | @border-gray-lighter: @gray-lighter;
34 | @border-gray-lightest: @gray-lightest;
35 |
36 | // Text color
37 | @text-color: @gray-darkest;
38 |
39 | // Hover
40 | @hover-highlight-color: @hover-color;
41 |
42 | // Section title
43 | @title-color: @section-title-color;
44 |
45 | // Links
46 | @link: @light-blue;
47 |
--------------------------------------------------------------------------------
/app/styles/less/modules/TabBar.less:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | Tabs
3 | ========================================================================== */
4 |
5 | tabbar {
6 | display: flex;
7 | flex-direction: column;
8 | flex-grow: 1;
9 | width: 100%;
10 | }
11 |
12 | /* Tab Links
13 | ========================================================================== */
14 |
15 | tabbar > tabs {
16 | background: @bg-gray-lightest;
17 | border-bottom: 1px solid @border-gray;
18 | display: flex;
19 | height: 23px;
20 | padding: 0 5px;
21 | align-items: stretch;
22 | -webkit-user-select: none;
23 | }
24 |
25 | tabbar > tabs > tab {
26 | border: 1px solid transparent;
27 | border-bottom: none;
28 | cursor: pointer;
29 | display: flex;
30 | font-family: @tabs-font-family-windows;
31 | margin-top: 2px;
32 | overflow: hidden;
33 | padding: 0 4px 1px;
34 | white-space: nowrap;
35 | align-items: center;
36 | }
37 |
38 | body[os='linux'] tabbar > tabs > tab {
39 | font-family: @tabs-font-family-linux;
40 | }
41 |
42 | body[os='mac'] tabbar > tabs > tab {
43 | font-family: @tabs-font-family-mac;
44 | }
45 |
46 | tabbar > tabs > tab[selected] {
47 | background: @bg-white;
48 | border-color: @border-gray;
49 | }
50 |
51 | /* Content for Tabs
52 | ========================================================================== */
53 |
54 | tabbar > contents {
55 | display: flex;
56 | overflow-y: auto;
57 | -webkit-transform: translateZ(0);
58 | flex: 1 1 0;
59 | }
60 |
61 | tabbar > contents > content {
62 | display: none;
63 | width: 100%;
64 | }
65 |
66 | tabbar > contents > [selected] {
67 | display: flex;
68 | flex: 1 1 0;
69 | }
70 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 2017
4 | },
5 | "env": {
6 | "es6": true
7 | },
8 | "rules": {
9 | "no-empty": [
10 | 1,
11 | {
12 | "allowEmptyCatch": true
13 | }
14 | ],
15 | "no-implicit-coercion": [
16 | 2,
17 | {
18 | "boolean": false,
19 | "string": true,
20 | "number": false
21 | }
22 | ],
23 | "no-with": 2,
24 | "brace-style": [
25 | 1,
26 | "1tbs",
27 | {
28 | "allowSingleLine": true
29 | }
30 | ],
31 | "no-mixed-spaces-and-tabs": 2,
32 | "no-multiple-empty-lines": 1,
33 | "no-multi-str": 2,
34 | "one-var": [2, "never"],
35 | "key-spacing": [
36 | 1,
37 | {
38 | "beforeColon": false,
39 | "afterColon": true
40 | }
41 | ],
42 | "space-unary-ops": [
43 | 2,
44 | {
45 | "words": false,
46 | "nonwords": false
47 | }
48 | ],
49 | "no-trailing-spaces": 2,
50 | "yoda": [2, "never"],
51 | "camelcase": [
52 | 2,
53 | {
54 | "properties": "never"
55 | }
56 | ],
57 | "comma-style": [2, "last"],
58 | "curly": [2, "all"],
59 | "dot-notation": 2,
60 | "eol-last": 2,
61 | "operator-linebreak": [2, "after"],
62 | "wrap-iife": 2,
63 | "space-infix-ops": 2,
64 | "keyword-spacing": [2, {}],
65 | "spaced-comment": [1, "always"],
66 | "space-before-blocks": [2, "always"],
67 | "consistent-this": [1, "that"],
68 | "linebreak-style": [2, "unix"],
69 | "quotes": [2, "single"]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: ui5-inspector
3 | Upstream-Contact: SAP OpenUI5
4 | Source: https://github.com/SAP/ui5-inspector
5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of
6 | SAP or third-party products or services developed outside of this project
7 | (“External Products”).
8 | “APIs” means application programming interfaces, as well as their respective
9 | specifications and implementing code that allows software to communicate with
10 | other software.
11 | API Calls to External Products are not licensed under the open source license
12 | that governs this project. The use of such API Calls and related External
13 | Products are subject to applicable additional agreements with the relevant
14 | provider of the External Products. In no event shall the open source license
15 | that governs this project grant any rights in or to any External Products,or
16 | alter, expand or supersede any terms of the applicable additional agreements.
17 | If you have a valid license agreement with SAP for the use of a particular SAP
18 | External Product, then you may make use of any API Calls included in this
19 | project’s code for that SAP External Product, subject to the terms of such
20 | license agreement. If you do not have a valid license agreement for the use of
21 | a particular SAP External Product, then you may only make use of any API Calls
22 | in this project for that SAP External Product for your internal, non-productive
23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants
24 | you any rights to use or access any SAP External Product, or provide any third
25 | parties the right to use of access any SAP External Product, through API Calls.
26 |
27 | Files: *
28 | Copyright: 2015 SAP SE or an SAP affiliate company and ui5-inspector contributors
29 | License: Apache-2.0
30 |
--------------------------------------------------------------------------------
/app/scripts/devtools/panel/initialize.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var utils = require('../../modules/utils/utils.js');
4 |
5 | // Create a port with background page for continuous message communication
6 | var port = utils.getPort();
7 |
8 | /**
9 | * Find the ID of the nearest UI5 control from the current selected element in Chrome elements panel.
10 | * @param {string} selectedElement - The ID of the selected element in Chrome elements panel
11 | * @returns {string} The ID of the nearest UI5 Control from the selectedElement
12 | * @private
13 | */
14 | function _getNearestUI5ControlID(selectedElement) {
15 | var element = selectedElement;
16 | while (!element.getAttribute('data-sap-ui')) {
17 | if (element.nodeName === 'BODY') {
18 | return undefined;
19 | }
20 | element = element.parentNode;
21 | }
22 | return element.id;
23 | }
24 |
25 | chrome.devtools.panels.create('UI5', '/images/icon-128.png', '/html/panel/ui5/index.html', function (panel) {
26 | panel.onHidden.addListener(function () {
27 | port.postMessage({
28 | action: 'on-ui5-devtool-hide'
29 | });
30 | });
31 | panel.onShown.addListener(function () {
32 | port.postMessage({
33 | action: 'on-ui5-devtool-show'
34 | });
35 | });
36 | });
37 |
38 | chrome.devtools.panels.elements.onSelectionChanged.addListener(function () {
39 | // To get the selected element in Chrome elements panel, is needed to use eval and call the function with $0 parameter
40 | var getNearestUI5Control = _getNearestUI5ControlID.toString() + '_getNearestUI5ControlID($0);';
41 |
42 | /* JSHINT evil: true */
43 | chrome.devtools.inspectedWindow.eval(getNearestUI5Control, {useContentScriptContext: true}, function (elementId) {
44 | if (elementId === undefined) {
45 | return;
46 | }
47 |
48 | port.postMessage({
49 | action: 'on-select-ui5-control-from-element-tab',
50 | nearestUI5Control: elementId
51 | });
52 | });
53 | /* JSHINT evil: false */
54 | });
55 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "UI5 Inspector",
4 | "version": "1.7.0",
5 | "background": {
6 | "service_worker": "/scripts/background/main.js"
7 | },
8 | "content_scripts": [
9 | {
10 | "js": [
11 | "/scripts/content/detectUI5.js"
12 | ],
13 | "all_frames": true,
14 | "matches": [
15 | "http://*/*",
16 | "https://*/*"
17 | ]
18 | }
19 | ],
20 | "content_security_policy": {
21 | "extension_pages": "default-src 'self'; img-src 'self' data:; style-src 'unsafe-inline';"
22 | },
23 | "description": "With the UI5 Inspector, you can easily debug and support your OpenUI5 or SAPUI5-based apps.",
24 | "devtools_page": "/html/devtools/index.html",
25 | "icons": {
26 | "16": "/images/icon-16.png",
27 | "128": "/images/icon-128.png"
28 | },
29 | "action": {
30 | "default_icon": {
31 | "19": "/images/icon-19.png",
32 | "38": "/images/icon-38.png"
33 | },
34 | "default_popup": "/html/popup/index.html"
35 | },
36 | "permissions": [
37 | "scripting",
38 | "contextMenus",
39 | "activeTab"
40 | ],
41 | "host_permissions": [
42 | "http://*/*",
43 | "https://*/*"
44 | ],
45 | "web_accessible_resources": [
46 | {
47 | "matches": [
48 | "http://*/*",
49 | "https://*/*"
50 | ],
51 | "resources": [
52 | "/scripts/injected/*.js",
53 | "/vendor/ToolsAPI.js",
54 | "/vendor/ace.js",
55 | "/vendor/ext-searchbox.js",
56 | "/vendor/mode-json.js",
57 | "/vendor/mode-xml.js",
58 | "/vendor/theme-chrome.js",
59 | "/vendor/theme-vibrant_ink.js",
60 | "/vendor/vkbeautify.js",
61 | "/modules/utils/multipartmixed2har.js"
62 | ]
63 | }
64 | ]
65 | }
--------------------------------------------------------------------------------
/tests/modules/background/pageAction.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var pageAction = require('../../../app/scripts/modules/background/pageAction.js');
4 |
5 | window.chrome = require('chrome-stub');
6 | window.chrome.action = {
7 | /**
8 | * Mock function of chrome api.
9 | */
10 | setTitle: function () {
11 | },
12 | disable: function () {
13 | },
14 | enable: function () {
15 | }
16 | };
17 |
18 | describe('pageAction', function () {
19 | it('should return a object', function () {
20 | pageAction.should.be.a('object');
21 | });
22 |
23 | describe('#create()', function () {
24 | var pageActionSetTitle;
25 |
26 | beforeEach(function () {
27 | pageActionSetTitle = sinon.spy(window.chrome.action, 'setTitle');
28 | });
29 |
30 | afterEach(function () {
31 | pageActionSetTitle.restore();
32 | });
33 |
34 | it('should call chrome.action.setTitle', function () {
35 | pageAction.create({
36 | framework: 'mock-framework',
37 | version: 'mock-version',
38 | tabId: 'mock-tabId'
39 | });
40 |
41 | pageActionSetTitle.callCount.should.equal(1);
42 | });
43 | });
44 |
45 | describe('#disable() & #enable()', function () {
46 | var disableStub;
47 | var enableStub;
48 |
49 | beforeEach(function () {
50 | disableStub = sinon.spy(window.chrome.action, 'disable');
51 | enableStub = sinon.spy(window.chrome.action, 'enable');
52 | });
53 |
54 | afterEach(function () {
55 | disableStub.restore();
56 | enableStub.restore();
57 | });
58 |
59 | it('should call chrome.action.disable', function () {
60 | pageAction.disable();
61 |
62 | disableStub.callCount.should.equal(1);
63 | });
64 |
65 | it('should call chrome.action.enable', function () {
66 | pageAction.enable();
67 |
68 | enableStub.callCount.should.equal(1);
69 | });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/app/scripts/modules/background/ContextMenu.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var oContextMenusCreated = {};
3 | /**
4 | * Context menu.
5 | * @param {Object} options
6 | * @constructor
7 | */
8 | function ContextMenu(options) {
9 | this._title = options.title;
10 | this._id = options.id;
11 | this._contexts = options.contexts;
12 |
13 | /**
14 | * This method will be fired when an instanced is clicked. The idea is to be overwritten from the instance.
15 | * @param {Object} info - Information sent when a context menu item is clicked.
16 | * @param {Object} tab - The details of the tab where the click took place. If the click did not take place in a tab,
17 | * this parameter will be missing.
18 | */
19 | this.onClicked = function (info, tab) {
20 | };
21 | }
22 |
23 | /**
24 | * Create context menu item.
25 | */
26 | ContextMenu.prototype.create = function () {
27 | var that = this;
28 |
29 | if (!oContextMenusCreated[that._id]) {
30 | chrome.contextMenus.create({
31 | title: that._title,
32 | id: that._id,
33 | contexts: that._contexts
34 | });
35 |
36 | chrome.contextMenus.onClicked.addListener(that._onClickHandler.bind(that));
37 | oContextMenusCreated[that._id] = true;
38 | }
39 | };
40 |
41 | /**
42 | * Delete all context menu items.
43 | */
44 | ContextMenu.prototype.removeAll = function () {
45 | chrome.contextMenus.removeAll();
46 | oContextMenusCreated = {};
47 | };
48 |
49 | /**
50 | * Set right clicked element.
51 | * @param {string} target
52 | */
53 | ContextMenu.prototype.setRightClickTarget = function (target) {
54 | this._rightClickTarget = target;
55 | };
56 |
57 | /**
58 | * Click handler.
59 | * @param {Object} info - Information sent when a context menu item is clicked.
60 | * @param {Object} tabs - The details of the tab where the click took place. If the click did not take place in a tab,
61 | * this parameter will be missing.
62 | */
63 | ContextMenu.prototype._onClickHandler = function (info, tabs) {
64 | if (info.menuItemId === this._id) {
65 | this.onClicked(info, tabs);
66 | }
67 | };
68 |
69 | module.exports = ContextMenu;
70 |
--------------------------------------------------------------------------------
/app/scripts/popup/popup.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var utils = require('../modules/utils/utils.js');
4 |
5 | window.chrome.tabs.query({active: true, currentWindow: true}).then(function (tabs) {
6 | // Create a port with background page for continuous message communication
7 | var port = utils.getPort();
8 |
9 | // Name space for message handler functions.
10 | var messageHandler = {
11 |
12 | /**
13 | * Ask for the framework information, as soon as the main script is injected.
14 | * @param {Object} message
15 | */
16 | 'on-main-script-injection': function (message) {
17 | port.postMessage({action: 'get-framework-information'});
18 | },
19 |
20 | /**
21 | * Visualize the framework information.
22 | * @param {Object} message
23 | */
24 | 'on-framework-information': function (message) {
25 | var linksDom;
26 | var library: HTMLElement = document.querySelector('library');
27 | var buildtime: HTMLElement = document.querySelector('buildtime');
28 |
29 | if (message.frameworkInformation.OpenUI5) {
30 | linksDom = document.querySelector('links[openui5]');
31 | library.innerText = 'OpenUI5';
32 | buildtime.innerText = message.frameworkInformation.OpenUI5;
33 | } else {
34 | linksDom = document.querySelector('links[sapui5]');
35 | library.innerText = 'SAPUI5';
36 | buildtime.innerText = message.frameworkInformation.SAPUI5;
37 | }
38 |
39 | linksDom.removeAttribute('hidden');
40 | }
41 | };
42 |
43 | // Listen for messages from the background page
44 | port.onMessage(function (message, messageSender, sendResponse) {
45 | // Resolve incoming messages
46 | utils.resolveMessage({
47 | message: message,
48 | messageSender: messageSender,
49 | sendResponse: sendResponse,
50 | actions: messageHandler
51 | });
52 | });
53 |
54 | port.postMessage({
55 | action: 'do-script-injection',
56 | tabId: tabs[0].id,
57 | file: '/scripts/content/main.js'
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/tests/modules/injected/rightClickHandler.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var rightClickHandler = require('../../../app/scripts/modules/injected/rightClickHandler.ts');
4 |
5 | describe('rightClickHandler', function () {
6 | it('should return a object', function () {
7 | rightClickHandler.should.be.a('object');
8 | });
9 |
10 | describe('#setClickedElementId()', function () {
11 | var fixtures = document.getElementById('fixtures');
12 |
13 | beforeEach(function () {
14 | fixtures.innerHTML = '';
15 | });
16 |
17 | afterEach(function () {
18 | fixtures.innerHTML = '';
19 | });
20 |
21 | it('should be a function', function () {
22 | rightClickHandler.setClickedElementId.should.be.a('function');
23 | });
24 |
25 | it('should set the ID of the UI5 control', function () {
26 | var element = fixtures.querySelector('#shell');
27 | rightClickHandler.setClickedElementId(element);
28 |
29 | rightClickHandler._clickedElementId.should.equal('shell');
30 | });
31 |
32 | it('should set the ID of the first UI5 parent control of the given target', function () {
33 | var element = fixtures.querySelector('#shell span');
34 | rightClickHandler.setClickedElementId(element);
35 |
36 | rightClickHandler._clickedElementId.should.equal('shell');
37 | });
38 |
39 | it('should set empty string if there are no UI5 controls', function () {
40 | rightClickHandler.setClickedElementId(fixtures.firstElementChild);
41 |
42 | rightClickHandler._clickedElementId.should.equal('');
43 | });
44 | });
45 |
46 | describe('#getClickedElementId()', function () {
47 | it('should be a function', function () {
48 | rightClickHandler.getClickedElementId.should.be.a('function');
49 | });
50 |
51 | it('should return the last clicked element ID', function () {
52 | rightClickHandler._clickedElementId = 'mock';
53 | rightClickHandler.getClickedElementId().should.equal('mock');
54 | });
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/ODataDetailView.js:
--------------------------------------------------------------------------------
1 | /* globals ResizeObserver */
2 |
3 | 'use strict';
4 |
5 | /**
6 | * @param {string} containerId - id of the DOM container
7 | * @constructor
8 | */
9 | function ODataDetailView(containerId) {
10 | this.oContainer = document.getElementById(containerId);
11 | this.oEditorDOM = document.createElement('div');
12 | this.oEditorDOM.id = 'editor';
13 | this.oContainer.appendChild(this.oEditorDOM);
14 |
15 | this.oEditorAltDOM = document.createElement('div');
16 | this.oEditorAltDOM.classList.add('editorAlt');
17 | this.oEditorAltMessageDOM = document.createElement('div');
18 | this.oContainer.appendChild(this.oEditorAltDOM);
19 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM);
20 |
21 | this.oEditor = ace.edit('editor');
22 | this.oEditor.getSession().setUseWrapMode(true);
23 |
24 | this._setTheme();
25 |
26 | const oResizeObserver = new ResizeObserver(function () {
27 | this.oEditor.resize();
28 | }.bind(this));
29 | oResizeObserver.observe(this.oEditorDOM);
30 | }
31 |
32 | /**
33 | * Updates data.
34 | * @param {Object} data - object structure as JSON
35 | */
36 | ODataDetailView.prototype.update = function (data) {
37 | const sResponseBody = data.responseBody;
38 | let sAltMessage;
39 |
40 | this.oEditorDOM.classList.toggle('hidden', !sResponseBody);
41 | this.oEditorAltDOM.classList.toggle('hidden', !!sResponseBody);
42 |
43 | if (sResponseBody) {
44 | this.oEditor.session.setMode('ace/mode/' + sResponseBody.type);
45 | this.oEditor.setValue(sResponseBody.content, 0);
46 | } else {
47 | sAltMessage = data.altMessage || 'No response body';
48 | this.oEditorAltMessageDOM.innerText = sAltMessage;
49 | }
50 |
51 | this.oEditor.clearSelection();
52 | };
53 |
54 | /**
55 | * Clears editor.
56 | */
57 | ODataDetailView.prototype.clear = function () {
58 | this.oEditor.setValue('', -1);
59 | };
60 |
61 | /**
62 | * Sets theme.
63 | */
64 | ODataDetailView.prototype._setTheme = function () {
65 | var bDarkMode = chrome.devtools.panels.themeName === 'dark';
66 |
67 | this.oEditor.setTheme(bDarkMode ? 'ace/theme/vibrant_ink' : 'ace/theme/chrome');
68 | };
69 |
70 | module.exports = ODataDetailView;
71 |
--------------------------------------------------------------------------------
/app/vendor/theme-vibrant_ink.js:
--------------------------------------------------------------------------------
1 | ace.define("ace/theme/vibrant_ink",["require","exports","module","ace/lib/dom"],function(r,e,m){e.isDark=true;e.cssClass="ace-vibrant-ink";e.cssText=".ace-vibrant-ink .ace_gutter {background: #1a1a1a;color: #BEBEBE}.ace-vibrant-ink .ace_print-margin {width: 1px;background: #1a1a1a}.ace-vibrant-ink {background-color: #0F0F0F;color: #FFFFFF}.ace-vibrant-ink .ace_cursor {color: #FFFFFF}.ace-vibrant-ink .ace_marker-layer .ace_selection {background: #6699CC}.ace-vibrant-ink.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #0F0F0F;}.ace-vibrant-ink .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-vibrant-ink .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #404040}.ace-vibrant-ink .ace_marker-layer .ace_active-line {background: #333333}.ace-vibrant-ink .ace_gutter-active-line {background-color: #333333}.ace-vibrant-ink .ace_marker-layer .ace_selected-word {border: 1px solid #6699CC}.ace-vibrant-ink .ace_invisible {color: #404040}.ace-vibrant-ink .ace_keyword,.ace-vibrant-ink .ace_meta {color: #FF6600}.ace-vibrant-ink .ace_constant,.ace-vibrant-ink .ace_constant.ace_character,.ace-vibrant-ink .ace_constant.ace_character.ace_escape,.ace-vibrant-ink .ace_constant.ace_other {color: #339999}.ace-vibrant-ink .ace_constant.ace_numeric {color: #99CC99}.ace-vibrant-ink .ace_invalid,.ace-vibrant-ink .ace_invalid.ace_deprecated {color: #CCFF33;background-color: #000000}.ace-vibrant-ink .ace_fold {background-color: #FFCC00;border-color: #FFFFFF}.ace-vibrant-ink .ace_entity.ace_name.ace_function,.ace-vibrant-ink .ace_support.ace_function,.ace-vibrant-ink .ace_variable {color: #FFCC00}.ace-vibrant-ink .ace_variable.ace_parameter {font-style: italic}.ace-vibrant-ink .ace_string {color: #66FF00}.ace-vibrant-ink .ace_string.ace_regexp {color: #44B4CC}.ace-vibrant-ink .ace_comment {color: #9933CC}.ace-vibrant-ink .ace_entity.ace_other.ace_attribute-name {font-style: italic;color: #99CC99}.ace-vibrant-ink .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYNDTc/oPAALPAZ7hxlbYAAAAAElFTkSuQmCC) right repeat-y}";var d=r("../lib/dom");d.importCssString(e.cssText,e.cssClass);});(function(){ace.require(["ace/theme/vibrant_ink"],function(m){if(typeof module=="object"&&typeof exports=="object"&&module){module.exports=m;}});})();
2 |
--------------------------------------------------------------------------------
/app/styles/less/modules/DataView.less:
--------------------------------------------------------------------------------
1 | data-view {
2 | flex-grow: 1;
3 | flex-wrap: nowrap;
4 | overflow: auto;
5 | width: 100%;
6 | -webkit-transform: translateZ(0);
7 | word-break: break-all;
8 |
9 | clickable-value,
10 | .controlId {
11 | color: @light-blue;
12 | }
13 |
14 | anchor,
15 | clickable-value,
16 | .controlId {
17 | cursor: pointer;
18 | text-decoration: underline;
19 | }
20 |
21 | anchor:hover,
22 | clickable-value:hover,
23 | .controlId:hover {
24 | text-decoration: none;
25 | }
26 |
27 | arrow {
28 | display: inline-block;
29 | vertical-align: top;
30 | }
31 |
32 | arrow~section-title{
33 | cursor: default;
34 | }
35 |
36 | & ul no-data {
37 | font-style: normal;
38 | padding: 0;
39 | text-align: left;
40 | }
41 |
42 | & ul li padding-base-left {
43 | padding-left: @padding-base-horizontal;
44 | }
45 |
46 | & > ul {
47 | border-bottom: 1px solid @gray-lighter;
48 | padding: 2px 2px 4px 4px;
49 | }
50 |
51 | key {
52 | white-space: nowrap;
53 | }
54 |
55 | li {
56 | & > ul {
57 | display: none;
58 | padding-left: 19px;
59 | padding-top: 2px;
60 |
61 | & section-title {
62 | color: @red-saturated
63 | }
64 | }
65 |
66 | & > ul[expanded] {
67 | display: flex;
68 | flex-direction: column;
69 | }
70 |
71 | & > ul[expanded] ~ collapsed-typeinfo {
72 | display: none;
73 | }
74 | }
75 |
76 | section-title{
77 | display: inline-block;
78 | }
79 |
80 | value[contentEditable=true]:focus {
81 | display: inline-block;
82 | outline: 1px solid @gray;
83 | outline-offset: 2px;
84 | box-shadow: 0 0 0 2px #fff, 0 1px 2px 3px rgba(0,0,0,0.6);
85 | }
86 |
87 | [gray] {
88 | color: @gray;
89 | }
90 | button.tools-button {
91 | display: block;
92 | }
93 |
94 | .disclaimer {
95 | font-size: 10px;
96 | padding-left: 5px;
97 | color: lightcoral;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/scripts/injected/detectUI5.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | /**
5 | * Create an object witch the initial needed information from the UI5 availability check.
6 | * @returns {Object}
7 | */
8 | function createResponseToContentScript() {
9 | var responseToContentScript = Object.create(null);
10 | var responseToContentScriptBody;
11 | var frameworkInfo;
12 | var versionInfo;
13 |
14 | responseToContentScript.detail = Object.create(null);
15 | responseToContentScriptBody = responseToContentScript.detail;
16 |
17 | if (window.sap && window.sap.ui) {
18 | responseToContentScriptBody.action = 'on-ui5-detected';
19 | responseToContentScriptBody.framework = Object.create(null);
20 |
21 | // Get framework version
22 | try {
23 | responseToContentScriptBody.framework.version = sap.ui.getCore().getConfiguration().getVersion().toString();
24 | } catch (e) {
25 | responseToContentScriptBody.framework.version = '';
26 | }
27 |
28 | // Get framework name
29 | try {
30 | versionInfo = sap.ui.getVersionInfo();
31 |
32 | // Use group artifact version for maven builds or name for other builds (like SAPUI5-on-ABAP)
33 | frameworkInfo = versionInfo.gav ? versionInfo.gav : versionInfo.name;
34 |
35 | responseToContentScriptBody.framework.name = frameworkInfo.indexOf('openui5') !== -1 ? 'OpenUI5' : 'SAPUI5';
36 | } catch (e) {
37 | responseToContentScriptBody.framework.name = 'UI5';
38 | }
39 |
40 | // Check if the version is supported
41 | responseToContentScriptBody.isVersionSupported = !!sap.ui.require;
42 |
43 | } else {
44 | responseToContentScriptBody.action = 'on-ui5-not-detected';
45 | }
46 |
47 | return responseToContentScript;
48 | }
49 |
50 | // Send information to content script
51 | document.dispatchEvent(new CustomEvent('detect-ui5-content', createResponseToContentScript()));
52 |
53 | // Listens for event from injected script
54 | document.addEventListener('do-ui5-detection-injected', function () {
55 | document.dispatchEvent(new CustomEvent('detect-ui5-content', createResponseToContentScript()));
56 | }, false);
57 | }());
58 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/FrameSelect.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * FrameSelect constructor
5 | * @param {string} id - The id of the DOM container
6 | * @param {object} options
7 | * @constructor
8 | */
9 | function FrameSelect(id, options) {
10 | this._sltDomRef = document.getElementById(id).querySelector('select');
11 | // Chrome treats the main window as the default frame with id 0
12 | this._selectedId = 0;
13 | this.onSelectionChange = options.onSelectionChange ? options.onSelectionChange : function () {};
14 |
15 | this._sltDomRef.addEventListener('change', function(event) {
16 | var selectedId = parseInt(event.target.value);
17 | var oldSelectedId = this._selectedId;
18 | this._selectedId = selectedId;
19 | this.onSelectionChange({selectedId, oldSelectedId});
20 | }.bind(this));
21 | }
22 |
23 | FrameSelect.prototype.getSelectedId = function () {
24 | return this._selectedId;
25 | };
26 |
27 | FrameSelect.prototype.setSelectedId = function (frameId) {
28 | this._sltDomRef.value = frameId;
29 | this._selectedId = frameId;
30 | };
31 |
32 | FrameSelect.prototype.setData = function (data) {
33 | var frameIds = Object.keys(data).map( x => parseInt(x));
34 | var selectedId;
35 | var oldSelectedId;
36 |
37 | this._sltDomRef.innerHTML = '';
38 | frameIds.forEach(function(frameId) {
39 | this._addOption(frameId, data[frameId]);
40 | }, this);
41 | if (frameIds.indexOf(this._selectedId) < 0) {
42 | // the previously selected id is no loger found
43 | // (e.g. frame deleted)
44 | // => reset selection to the top frame
45 | selectedId = 0;
46 | oldSelectedId = this._selectedId;
47 | this.setSelectedId(selectedId);
48 | this.onSelectionChange({selectedId, oldSelectedId});
49 | }
50 | this._sltDomRef.hidden = false;
51 | };
52 |
53 | FrameSelect.prototype._addOption = function (frameId, data) {
54 | var option = document.createElement('option');
55 | option.value = frameId;
56 | option.innerText = this._getFrameLabel(frameId, data.url);
57 | this._sltDomRef.appendChild(option);
58 | };
59 |
60 | FrameSelect.prototype._getFrameLabel = function (frameId, frameUrl) {
61 | if (frameId === 0) {
62 | return 'top';
63 | }
64 | var aUrl = frameUrl.split('/');
65 | return aUrl[aUrl.length - 1];
66 | };
67 |
68 | module.exports = FrameSelect;
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](http://gruntjs.com/)
4 | [](https://travis-ci.org/SAP/ui5-inspector)
5 | [](https://coveralls.io/github/SAP/ui5-inspector?branch=master)
6 | [](https://api.reuse.software/info/github.com/SAP/ui5-inspector)
7 |
8 | # About UI5 Inspector
9 |
10 | UI5 Inspector is a standard Chrome or Edge extension for debugging and getting to know UI5 applications.
11 |
12 | It's free and open source: UI5 Inspector is licensed under the Apache License, Version 2.0.
13 | See [LICENSE.txt](https://github.com/SAP/ui5-inspector/blob/master/LICENSE.txt) for more information.
14 |
15 | ## Direct download and use
16 |
17 | The latest released version can be downloaded and installed as follows:
18 |
19 | 1. Download zip file from [Releases](https://github.com/SAP/ui5-inspector/releases)
20 | 2. Unpack to a directory
21 | 3. In Chrome open as url: `chrome://extensions/`. Alternatively, you can access `edge://extensions/` when in Edge. The extensions page is also reachable via the browser's menu.
22 | 4. Check “Developer mode” setting and then choose "Load unpacked extension..."
23 | 5. From the newly opened window select the folder to which the zip file was unpacked
24 | 6. Restart Chrome or Edge
25 | 7. Open a OpenUI5/SAPUI5 based web application like: [https://openui5.hana.ondemand.com/explored.html](https://openui5.hana.ondemand.com/explored.html)
26 |
27 | ## Local development and use
28 |
29 | You can get the source code locally and contribute to the project.
30 |
31 | 1. Clone the project locally: `git clone git@github.com:SAP/ui5-inspector.git`
32 | 2. Install dependencies with the following commands: `npm install`
33 | 3. In Chrome open as url: `chrome://extensions/`
34 | 4. Check “Developer mode” and then click "Load unpacked extension..."
35 | 5. From the newly opened window select the **dist** folder from the locally cloned project
36 | 6. Restart Chrome
37 | 7. Open a OpenUI5/SAPUI5 based web application like: [https://openui5.hana.ondemand.com/explored.html](https://openui5.hana.ondemand.com/explored.html)
38 |
39 | ## License
40 |
41 | [](./LICENSE.txt)
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui5inspector",
3 | "version": "1.7.0",
4 | "author": "SAP SE",
5 | "license": "Apache-2.0",
6 | "contributors": [
7 | "SAP SE <*@sap.com>"
8 | ],
9 | "dependencies": {
10 | "lodash": "^4.17.21"
11 | },
12 | "devDependencies": {
13 | "@actions/core": "^1.9.1",
14 | "@actions/exec": "^1.1.1",
15 | "@actions/github": "^5.0.3",
16 | "@commitlint/cli": "^16.2.3",
17 | "@commitlint/config-conventional": "^16.2.1",
18 | "@semantic-release/changelog": "^6.0.1",
19 | "@semantic-release/commit-analyzer": "^9.0.2",
20 | "@semantic-release/exec": "^6.0.3",
21 | "@semantic-release/git": "^10.0.1",
22 | "@semantic-release/github": "^8.0.5",
23 | "@semantic-release/release-notes-generator": "^10.0.3",
24 | "browserify": "^16.5.1",
25 | "browserify-istanbul": "^3.0.1",
26 | "chai": "~4.2.0",
27 | "chrome-stub": "^1.0.2",
28 | "coveralls": "^3.1.1",
29 | "grunt": "^1.4.1",
30 | "grunt-banner": "~0.6.0",
31 | "grunt-browserify": "^6.0.0",
32 | "grunt-contrib-clean": "~2.0.0",
33 | "grunt-contrib-compress": "~1.6.0",
34 | "grunt-contrib-connect": "~2.1.0",
35 | "grunt-contrib-copy": "~1.0.0",
36 | "grunt-contrib-jshint": "~2.1.0",
37 | "grunt-contrib-less": "~2.0.0",
38 | "grunt-contrib-watch": "~1.1.0",
39 | "grunt-coveralls": "^2.0.0",
40 | "grunt-eslint": "^23.0.0",
41 | "grunt-karma": "^4.0.2",
42 | "grunt-newer": "~1.3.0",
43 | "grunt-text-replace": "~0.4.0",
44 | "husky": "^7.0.4",
45 | "jshint-stylish": "~2.2.1",
46 | "karma": "^6.4.0",
47 | "karma-browserify": "^8.1.0",
48 | "karma-chai": "^0.1.0",
49 | "karma-chrome-launcher": "^3.1.1",
50 | "karma-coverage": "^2.2.0",
51 | "karma-coverage-istanbul-reporter": "^3.0.3",
52 | "karma-mocha": "^2.0.1",
53 | "karma-sinon": "^1.0.5",
54 | "load-grunt-config": "~3.0.1",
55 | "load-grunt-tasks": "~5.1.0",
56 | "mocha": "^11.1.0",
57 | "prettify-xml": "^1.2.0",
58 | "semantic-release": "^19.0.3",
59 | "sinon": "~9.0.2",
60 | "time-grunt": "~2.0.0",
61 | "typescript": "^4.9.5"
62 | },
63 | "engines": {
64 | "node": ">=10.0.0"
65 | },
66 | "repository": {
67 | "type": "git",
68 | "url": "https://github.com/SAP/ui5-inspector"
69 | },
70 | "scripts": {
71 | "generate:typescript": "tsc",
72 | "postinstall": "npm run generate:typescript && node -e \"require('grunt').tasks(['dist']);\"",
73 | "prepare": "husky install",
74 | "husky:pre-commit": "grunt test",
75 | "husky:commit-msg": "commitlint -e"
76 | }
77 | }
--------------------------------------------------------------------------------
/tests/modules/injected/ui5inspector.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var ui5inspector = require('../../../app/scripts/modules/injected/ui5inspector.js');
4 |
5 | /**
6 | * Mock function of an eventListener callback.
7 | * @returns {string}
8 | */
9 | var callBackMock = function mockFunctionName() {
10 | return 'mock';
11 | };
12 |
13 | /**
14 | * Mock function of an eventListener callback.
15 | * @returns {string}
16 | */
17 | var callBackSecondMock = function secondMockFunctionName() {
18 | return 'mock';
19 | };
20 |
21 | describe('ui5inspector', function () {
22 | it('should return a object', function () {
23 | ui5inspector.should.be.a('object');
24 | });
25 |
26 | describe('#createReferences()', function () {
27 | beforeEach(function () {
28 | ui5inspector.createReferences();
29 | });
30 |
31 | afterEach(function () {
32 | window.ui5inspector = undefined;
33 | });
34 |
35 | it('should be a function', function () {
36 | ui5inspector.createReferences.should.be.a('function');
37 | });
38 |
39 | it('should create ui5inspector global object', function () {
40 | window.ui5inspector.should.be.a('object');
41 | });
42 |
43 | it('should not overwrite window.ui5inspector if it is already set', function () {
44 | window.ui5inspector.mock = 'mock';
45 | ui5inspector.createReferences();
46 |
47 | window.ui5inspector.mock.should.equal('mock');
48 | });
49 | });
50 |
51 | describe('#registerEventListener()', function () {
52 |
53 | beforeEach(function () {
54 | ui5inspector.createReferences();
55 | });
56 |
57 | afterEach(function () {
58 | window.ui5inspector = undefined;
59 | });
60 |
61 | it('should be a function', function () {
62 | ui5inspector.registerEventListener.should.be.a('function');
63 | });
64 |
65 | it('should create a references for every event that is registered', function () {
66 | ui5inspector.registerEventListener('mock', callBackMock);
67 |
68 | window.ui5inspector.events.mock.callback.should.equal('mockFunctionName');
69 | window.ui5inspector.events.mock.state.should.equal('registered');
70 | });
71 |
72 | it('should not register two events with the same name', function () {
73 | ui5inspector.registerEventListener('mock', callBackMock);
74 | ui5inspector.registerEventListener('mock', callBackSecondMock);
75 |
76 | window.ui5inspector.events.mock.callback.should.not.equal('secondMockFunctionName');
77 | });
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/app/vendor/theme-chrome.js:
--------------------------------------------------------------------------------
1 | ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() {
2 | ace.require(["ace/theme/chrome"], function(m) {
3 | if (typeof module == "object" && typeof exports == "object" && module) {
4 | module.exports = m;
5 | }
6 | });
7 | })();
8 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/XMLDetailView.js:
--------------------------------------------------------------------------------
1 | /* globals ResizeObserver */
2 |
3 | 'use strict';
4 | const formatXML = require('prettify-xml');
5 | const NOXMLVIEWMESSAGE = 'Select a \'sap.ui.core.mvc.XMLView\' to see its XML content. Click to filter on XMLViews';
6 |
7 | /**
8 | * @param {string} containerId - id of the DOM container
9 | * @constructor
10 | */
11 | function XMLDetailView(containerId) {
12 | this.oContainer = document.getElementById(containerId);
13 | this.oEditorDOM = document.createElement('div');
14 | this.oEditorDOM.id = 'xmlEditor';
15 | this.oEditorDOM.classList.toggle('hidden', true);
16 | this.oContainer.appendChild(this.oEditorDOM);
17 |
18 | this.oEditorAltDOM = document.createElement('div');
19 | this.oEditorAltDOM.classList.add('editorAlt');
20 | this.oEditorAltDOM.classList.toggle('hidden', false);
21 | this.oEditorAltMessageDOM = document.createElement('div');
22 | this.oEditorAltMessageDOM.innerText = NOXMLVIEWMESSAGE;
23 |
24 | this.oEditorAltMessageDOM.addEventListener('click', function() {
25 | var searchField = document.getElementById('elementsRegistrySearch');
26 | var filterCheckbox = document.getElementById('elementsRegistryCheckbox');
27 | searchField.value = 'sap.ui.core.mvc.XMLView';
28 | if (!filterCheckbox.checked) {
29 | filterCheckbox.click();
30 | }
31 | return false;
32 | });
33 | this.oContainer.appendChild(this.oEditorAltDOM);
34 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM);
35 |
36 | this.oEditor = ace.edit(this.oEditorDOM.id);
37 | this.oEditor.getSession().setUseWrapMode(true);
38 |
39 | this._setTheme();
40 |
41 | const oResizeObserver = new ResizeObserver(function () {
42 | this.oEditor.resize();
43 | }.bind(this));
44 | oResizeObserver.observe(this.oEditorDOM);
45 | }
46 |
47 | /**
48 | * Updates data.
49 | * @param {Object} data - object structure as JSON
50 | */
51 | XMLDetailView.prototype.update = function (data) {
52 | const xml = data.xml && formatXML(data.xml);
53 | let sAltMessage;
54 |
55 | this.oEditorDOM.classList.toggle('hidden', !xml);
56 | this.oEditorAltDOM.classList.toggle('hidden', !!xml);
57 |
58 | if (xml) {
59 | this.oEditor.session.setMode('ace/mode/xml');
60 | this.oEditor.setValue(xml, 0);
61 | } else {
62 | sAltMessage = data.altMessage || NOXMLVIEWMESSAGE;
63 | this.oEditorAltMessageDOM.innerText = sAltMessage;
64 | }
65 |
66 | this.oEditor.clearSelection();
67 | };
68 |
69 | /**
70 | * Clears editor.
71 | */
72 | XMLDetailView.prototype.clear = function () {
73 | this.oEditor.setValue('', -1);
74 | };
75 |
76 | /**
77 | * Sets theme.
78 | */
79 | XMLDetailView.prototype._setTheme = function () {
80 | var bDarkMode = chrome.devtools.panels.themeName === 'dark';
81 |
82 | this.oEditor.setTheme(bDarkMode ? 'ace/theme/vibrant_ink' : 'ace/theme/chrome');
83 | };
84 |
85 | module.exports = XMLDetailView;
86 |
--------------------------------------------------------------------------------
/app/scripts/modules/content/highLighter.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Singleton helper to highlight controls DOM elements
5 | */
6 | var Highlighter = {
7 | // Reference for the highlighter DOM element
8 | _highLighterDomEl: null,
9 |
10 | /**
11 | * Hide the highlighter.
12 | * @public
13 | */
14 | hide: function () {
15 | this._highLighterDomEl && (this._highLighterDomEl.style.display = 'none');
16 | },
17 |
18 | /**
19 | * Show the highlighter.
20 | * @private
21 | */
22 | _show: function () {
23 | this._highLighterDomEl && (this._highLighterDomEl.style.display = 'block');
24 | },
25 |
26 | /**
27 | * Create DOM element for visual highlighting.
28 | * @private
29 | */
30 | _create: function () {
31 | var highLighter = document.createElement('div');
32 | highLighter.style.cssText = 'box-sizing: border-box;border:1px solid blue;background: rgba(20, 20, 200, 0.4);position: absolute';
33 |
34 | var highLighterWrapper = document.createElement('div');
35 | highLighterWrapper.id = 'ui5-highlighter';
36 | highLighterWrapper.style.cssText = 'position: fixed;top:0;right:0;bottom:0;left:0;z-index: 1000;overflow: hidden;';
37 | highLighterWrapper.appendChild(highLighter);
38 |
39 | document.body.appendChild(highLighterWrapper);
40 |
41 | // Save reference for later usage
42 | this._highLighterDomEl = document.getElementById('ui5-highlighter');
43 |
44 | // Add event handler
45 | this._highLighterDomEl.onmouseover = this.hide.bind(this);
46 | },
47 |
48 | /**
49 | * Set the position of the visual highlighter.
50 | * @param {string} elementId - The id of the DOM element that need to be highlighted
51 | * @returns {exports}
52 | */
53 | setDimensions: function (elementId) {
54 | var highlighter;
55 | var targetDomElement;
56 | var targetRect;
57 |
58 | // the hightlighter DOM element may already have been created in a previous DevTools session
59 | // (followed by closing and reopening the DevTools)
60 | this._highLighterDomEl || (this._highLighterDomEl = document.getElementById('ui5-highlighter'));
61 |
62 | if (!this._highLighterDomEl) {
63 | this._create();
64 | } else {
65 | this._show();
66 | }
67 |
68 | highlighter = this._highLighterDomEl.firstElementChild;
69 | targetDomElement = document.getElementById(elementId);
70 |
71 | if (targetDomElement) {
72 | targetRect = targetDomElement.getBoundingClientRect();
73 |
74 | highlighter.style.top = targetRect.top + 'px';
75 | highlighter.style.left = targetRect.left + 'px';
76 | highlighter.style.height = targetRect.height + 'px';
77 | highlighter.style.width = targetRect.width + 'px';
78 | }
79 |
80 | return this;
81 | }
82 | };
83 |
84 | module.exports = Highlighter;
85 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/TabBar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * TabBar.
5 | * @param {string} containerId
6 | * @constructor
7 | */
8 | function TabBar(containerId) {
9 | this._container = document.getElementById(containerId);
10 | this._contentsContainer = this._container.querySelector('contents');
11 | this._tabsContainer = this._container.querySelector('tabs');
12 | this.init();
13 | }
14 |
15 | /**
16 | * Initialize TabBar.
17 | */
18 | TabBar.prototype.init = function () {
19 | this.setActiveTab(this.getActiveTab());
20 |
21 | // Add event handler on the tab container
22 | this._tabsContainer.onclick = this._onTabsClick.bind(this);
23 | };
24 |
25 | /**
26 | * Get current active tab ID.
27 | * @returns {string}
28 | */
29 | TabBar.prototype.getActiveTab = function () {
30 | return this._activeTabId ? this._activeTabId : this._tabsContainer.querySelector('[selected]').id;
31 | };
32 |
33 | /**
34 | * Set active tab ID.
35 | * @param {string} newActiveTabId
36 | * @returns {TabBar}
37 | */
38 | TabBar.prototype.setActiveTab = function (newActiveTabId) {
39 | if (!newActiveTabId) {
40 | return;
41 | }
42 |
43 | if (typeof newActiveTabId !== 'string') {
44 | console.warn('parameter error: The parameter must be a string');
45 | return;
46 | }
47 |
48 | if (!this._tabsContainer.querySelector('#' + newActiveTabId)) {
49 | console.warn('parameter error: The parameter must be a valid ID of a child tab element');
50 | return;
51 | }
52 |
53 | // Check for double clicking on active tab
54 | if (newActiveTabId === this.getActiveTab()) {
55 | var activeContent = this._contentsContainer.querySelector('[for="' + this.getActiveTab() + '"]');
56 |
57 | if (activeContent.getAttribute('selected')) {
58 | return;
59 | }
60 | }
61 |
62 | this._changeActiveTab(newActiveTabId);
63 | this._activeTabId = newActiveTabId;
64 |
65 | return this;
66 | };
67 |
68 | /**
69 | * Event handler for mouse click on a tabs.
70 | * @param {Object} event - click event
71 | * @private
72 | */
73 | TabBar.prototype._onTabsClick = function (event) {
74 | var targetID = event.target.id;
75 | this.setActiveTab(targetID);
76 | };
77 |
78 | /**
79 | * Change visible tab and content.
80 | * @param {string} tabId - The Id of the desired tab
81 | */
82 | TabBar.prototype._changeActiveTab = function (tabId) {
83 | var currentActiveTab = this._tabsContainer.querySelector('[selected]');
84 | var currentActiveContent = this._contentsContainer.querySelector('[for="' + this.getActiveTab() + '"]');
85 | var newActiveTab = this._tabsContainer.querySelector('#' + tabId);
86 | var newActiveContent = this._contentsContainer.querySelector('[for="' + tabId + '"]');
87 |
88 | currentActiveTab.removeAttribute('selected');
89 | currentActiveContent.removeAttribute('selected');
90 |
91 | newActiveTab.setAttribute('selected', 'true');
92 | newActiveContent.setAttribute('selected', 'true');
93 | };
94 |
95 | module.exports = TabBar;
96 |
--------------------------------------------------------------------------------
/tests/modules/content/highLighter.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // TODO the highLighter module needs refactoring
4 | var highLighter = require('../../../app/scripts/modules/content/highLighter.ts');
5 |
6 | describe('highLighter', function () {
7 | it('should return a object', function () {
8 | highLighter.should.be.a('object');
9 | });
10 |
11 | describe('#setDimensions()', function () {
12 | var fixtures = document.getElementById('fixtures');
13 |
14 | before(function () {
15 | fixtures.innerHTML = '';
16 | });
17 |
18 | after(function () {
19 | fixtures.innerHTML = '';
20 | document.getElementById('ui5-highlighter').parentNode.removeChild(document.getElementById('ui5-highlighter'));
21 | });
22 |
23 | it('should create a DOM elements', function () {
24 | highLighter.setDimensions('shell');
25 |
26 | document.body.querySelector('#ui5-highlighter').should.exist;
27 | document.body.querySelector('#ui5-highlighter > div').should.exist;
28 | });
29 |
30 | it('should set proper sizes to the "#ui5-highlighter > div" element', function () {
31 | highLighter.setDimensions('shell');
32 |
33 | document.body.querySelector('#ui5-highlighter > div').style.width.should.equal('50px');
34 | document.body.querySelector('#ui5-highlighter > div').style.height.should.equal('50px');
35 | });
36 |
37 | it('should position the inner div according to target', function () {
38 | var mock = document.getElementById('shell').getBoundingClientRect();
39 | highLighter.setDimensions('shell');
40 |
41 | document.body.querySelector('#ui5-highlighter > div').style.top.should.equal(mock.top + 'px');
42 | document.body.querySelector('#ui5-highlighter > div').style.left.should.equal(mock.left + 'px');
43 | });
44 |
45 | it('should not add CSS styles if the target can not be found', function () {
46 | document.body.querySelector('#ui5-highlighter > div').removeAttribute('style');
47 | highLighter.setDimensions('shell-mock');
48 |
49 | document.body.querySelector('#ui5-highlighter > div').style.width.should.equal('');
50 | document.body.querySelector('#ui5-highlighter > div').style.height.should.equal('');
51 | document.body.querySelector('#ui5-highlighter > div').style.top.should.equal('');
52 | document.body.querySelector('#ui5-highlighter > div').style.left.should.equal('');
53 | });
54 |
55 | it('should hide the highlighter on hover', function () {
56 | highLighter.setDimensions('shell');
57 | document.body.querySelector('#ui5-highlighter').onmouseover();
58 |
59 | document.body.querySelector('#ui5-highlighter').style.display.should.equal('none');
60 | });
61 |
62 | it('should create only one highlighter', function () {
63 | highLighter.setDimensions('shell');
64 | highLighter.setDimensions('shell');
65 |
66 | document.body.querySelectorAll('#ui5-highlighter').length.should.equal(1);
67 | });
68 |
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/app/scripts/modules/ui/ControllerDetailView.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 | const NOCONTROLLERMESSAGE = 'Select a \'sap.ui.core.mvc.XMLView\' to see its Controller content. Click to filter on XMLViews';
4 | const CONTROLLERNAME = 'Name:';
5 | const CONTROLLERPATH = 'Relative Path:';
6 | /**
7 | * @param {string} containerId - id of the DOM container
8 | * @constructor
9 | */
10 | function ControllerDetailView(containerId) {
11 | this.oContainer = document.getElementById(containerId);
12 | this.oEditorDOM = document.createElement('div');
13 | this.oEditorDOM.id = 'controllerEditor';
14 | this.oEditorDOM.classList.toggle('hidden', true);
15 | this.oContainer.appendChild(this.oEditorDOM);
16 |
17 | this.oNamePlaceholderDOM = document.createElement('div');
18 | this.oNamePlaceholderDOM.classList.add('longTextReduce');
19 | this.oNamePlaceholderDOM.onclick = this._selectAllText;
20 | this.oPathPlaceholderDOM = document.createElement('div');
21 | this.oPathPlaceholderDOM.classList.add('longTextReduce');
22 | this.oPathPlaceholderDOM.onclick = this._selectAllText;
23 | this.oNameDOM = document.createElement('div');
24 | this.oNameDOM.classList.add('firstColAlignment');
25 | this.oNameDOM.innerText = CONTROLLERNAME;
26 | this.oPathDOM = document.createElement('div');
27 | this.oPathDOM.classList.add('firstColAlignment');
28 | this.oPathDOM.innerText = CONTROLLERPATH;
29 | this.oEditorDOM.appendChild(this.oNameDOM);
30 | this.oEditorDOM.appendChild(this.oNamePlaceholderDOM);
31 | this.oEditorDOM.appendChild(this.oPathDOM);
32 | this.oEditorDOM.appendChild(this.oPathPlaceholderDOM);
33 |
34 | this.oEditorAltDOM = document.createElement('div');
35 | this.oEditorAltDOM.classList.add('editorAlt');
36 | this.oEditorAltDOM.classList.toggle('hidden', false);
37 | this.oEditorAltMessageDOM = document.createElement('div');
38 | this.oEditorAltMessageDOM.innerText = NOCONTROLLERMESSAGE;
39 |
40 | this.oEditorAltMessageDOM.addEventListener('click', function() {
41 | var searchField = document.getElementById('elementsRegistrySearch');
42 | var filterCheckbox = document.getElementById('elementsRegistryCheckbox');
43 | searchField.value = 'sap.ui.core.mvc.XMLView';
44 | if (!filterCheckbox.checked) {
45 | filterCheckbox.click();
46 | }
47 | return false;
48 | });
49 | this.oContainer.appendChild(this.oEditorAltDOM);
50 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM);
51 | }
52 |
53 | /**
54 | * Updates data.
55 | * @param {Object} data - object structure as JSON
56 | */
57 | ControllerDetailView.prototype.update = function (controllerInfo) {
58 |
59 | var bIsDataValid = !!(controllerInfo.sControllerName && controllerInfo.sControllerRelPath);
60 |
61 | this.oEditorDOM.classList.toggle('hidden', !bIsDataValid);
62 | this.oEditorAltDOM.classList.toggle('hidden', bIsDataValid);
63 |
64 | if (bIsDataValid) {
65 | this.oNamePlaceholderDOM.innerText = controllerInfo.sControllerName;
66 | this.oPathPlaceholderDOM.innerText = controllerInfo.sControllerRelPath;
67 | }
68 | };
69 |
70 | ControllerDetailView.prototype._selectAllText = function (oEvent) {
71 | var range = document.createRange();
72 | range.selectNode(oEvent.target);
73 | window.getSelection().removeAllRanges();
74 | window.getSelection().addRange(range);
75 | };
76 |
77 |
78 | module.exports = ControllerDetailView;
79 |
--------------------------------------------------------------------------------
/app/styles/less/modules/ElementsRegistry.less:
--------------------------------------------------------------------------------
1 |
2 | elements-registry-master-view {
3 | display: flex;
4 | overflow: hidden;
5 | position: relative;
6 | width: 100%;
7 |
8 | #elementsRegistryContent {
9 | display: flex;
10 | flex-direction: column;
11 | flex: 1 1 0;
12 | flex-wrap: nowrap;
13 | }
14 |
15 | span[is="dt-icon-label"] { flex: 0 0 auto; }
16 | [is="ui-icon"] { display: inline-block; flex-shrink: 0; }
17 | [is="ui-icon"].icon-mask { background-color: rgb(110, 110, 110); -webkit-mask-position: var(--spritesheet-position); }
18 | .spritesheet-smallicons:not(.icon-mask) { background-image: url("/images/smallIcons.svg"); }
19 | .spritesheet-smallicons.icon-mask { -webkit-mask-image: url("/images/smallIcons.svg"); }
20 | .spritesheet-largeicons.icon-mask { -webkit-mask-image: url("/images/largeIcons.svg"); }
21 | .spritesheet-mediumicons.icon-mask { -webkit-mask-image: url("/images/mediumIcons.svg"); }
22 | .spritesheet-mediumicons:not(.icon-mask) { background-image: url(/images/mediumIcons.svg); }
23 | :host-context(.force-white-icons) [is="ui-icon"].spritesheet-smallicons, .force-white-icons [is="ui-icon"].spritesheet-smallicons, [is="ui-icon"].force-white-icons.spritesheet-smallicons, -theme-preserve { -webkit-mask-image: url("/images/smallIcons.svg"); -webkit-mask-position: var(--spritesheet-position); background: rgb(250, 250, 250) !important; }
24 | ::selection { background-color: @bg-datagrid; }
25 | [data-aria-utils-animation-hack] { animation: 0s ease 0s 1 normal none running ANIMATION-HACK; }
26 | cursor: default; font-family: ".SFNSDisplay-Regular", "Helvetica Neue", "Lucida Grande", sans-serif; font-size: 12px; tab-size: 4; user-select: none;
27 |
28 | .largeicon-clear:hover:not(:active) { background-color: #333; }
29 |
30 | [is=ui-icon]:not(.icon-mask) {
31 | background-position: var(--spritesheet-position);
32 | }
33 |
34 | filter {
35 | border-bottom: solid 1px @border-grey-light;
36 | display: flex;
37 | left: 0;
38 | line-height: 1;
39 | padding: 5px;
40 | align-items: center;
41 | }
42 |
43 | filter > start,
44 | filter > end {
45 | display: flex;
46 | }
47 |
48 | filter > start {
49 | flex: 1 1 0;
50 | }
51 |
52 | filter [type="search"] {
53 | border: solid 1px @border-gray-dark;
54 | border-radius: 2px;
55 | background-color: @bg-search-field;
56 | color: @text-color;
57 | padding-left: 5px;
58 | padding-right: 5px;
59 | }
60 |
61 | filter [type="checkbox"] {
62 | margin-right: 2px;
63 | }
64 |
65 | filter label {
66 | display: flex;
67 | line-height: 20px;
68 | padding: 0 6px;
69 | white-space: nowrap;
70 | align-items: center;
71 | }
72 |
73 | }
74 |
75 | elements-registry-master-view:not([show-filtered-elements]) {
76 | .matching {
77 | background: @bg-highlight-color !important;
78 | }
79 |
80 | .selected {
81 | background: @lighter-blue !important;
82 | ::selection {
83 | background: @gray-dark !important;
84 | }
85 | }
86 | }
87 |
88 | .elementsRegistry splitter {
89 | background-color: @bg-datagrid-header;
90 | }
91 |
--------------------------------------------------------------------------------
/app/styles/less/modules/SplitContainer.less:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | SplitContainer
3 | ========================================================================== */
4 |
5 | .user-is-resizing-vertically,
6 | .user-is-resizing-horizontally {
7 | cursor: ns-resize;
8 | -webkit-user-select: none;
9 | }
10 |
11 | .user-is-resizing-horizontally {
12 | cursor: ew-resize;
13 | }
14 |
15 | splitter {
16 | display: flex;
17 | flex-direction: row;
18 | overflow: hidden;
19 | position: relative;
20 | width: 100%;
21 | flex: 1 1 0;
22 |
23 | & > start {
24 | display: flex;
25 | overflow: hidden;
26 | position: relative;
27 | transform: translateZ(0);
28 | flex: 1 100px;
29 | }
30 |
31 | & > end {
32 | display: flex;
33 | overflow: hidden;
34 | position: relative;
35 | transform: translateZ(0);
36 | }
37 |
38 | /* right */
39 |
40 | & > end[withHeader] {
41 | padding-top: 20px;
42 | }
43 |
44 | & > end > header {
45 | background-color: @gray-lightest;
46 | border-bottom: solid 1px @gray;
47 | left: 0;
48 | line-height: 20px;
49 | padding-left: 5px;
50 | position: fixed;
51 | right: 0;
52 | top: 0;
53 | }
54 |
55 | //close button
56 | & > end > .toolbar-item {
57 | background: url("/images/close.png") no-repeat center;
58 | content: '';
59 | cursor: pointer;
60 | display: none;
61 | height: 15px;
62 | opacity: 0.5;
63 | position: absolute;
64 | right: 2px;
65 | top: 2px;
66 | transition: opacity 0.2s;
67 | width: 15px;
68 |
69 | &:hover {
70 | opacity: 1;
71 | }
72 | }
73 |
74 | & > end > content {
75 | display: flex;
76 | flex: 1;
77 | }
78 |
79 | & > end > content section {
80 | border-bottom: solid 1px #a3a3a3;
81 | padding: 4px 5px;
82 | white-space: nowrap;
83 | }
84 |
85 | & > end > content section:last-child {
86 | border-bottom: none;
87 | }
88 |
89 | & > end > content section[selected] {
90 | flex: 1;
91 | }
92 |
93 | /* end right */
94 |
95 | & > divider {
96 | background: #a3a3a3;
97 | display: flex;
98 | position: relative;
99 | width: 1px;
100 | }
101 |
102 | & > divider handler {
103 | bottom: 0;
104 | cursor: ew-resize;
105 | left: -3px;
106 | position: absolute;
107 | right: -3px;
108 | top: 0;
109 | }
110 |
111 | &.verticalOrientation {
112 | flex-direction: column;
113 |
114 | & > start {
115 | flex: 1 50px;
116 | }
117 |
118 | & > divider {
119 | height: 1px;
120 | width: auto;
121 | }
122 |
123 | & > end {
124 | min-height: 50px;
125 | height: 50%;
126 | min-width: 0;
127 | width: auto;
128 | }
129 |
130 | & > divider handler {
131 | bottom: -3px;
132 | left: 0;
133 | right: 0;
134 | top: -3px;
135 | cursor: ns-resize;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 |
3 | module.exports = function (config) {
4 | var configuration = {
5 |
6 | // Base path that will be used to resolve all patterns (eg. files, exclude)
7 | basePath: './',
8 |
9 | // Frameworks to use
10 | frameworks: [
11 | 'browserify',
12 | 'mocha',
13 | 'chai',
14 | 'sinon'
15 | ],
16 |
17 | plugins: [
18 | 'browserify',
19 | 'browserify-istanbul',
20 | 'karma-browserify',
21 | 'karma-coverage-istanbul-reporter',
22 | 'karma-mocha',
23 | 'karma-chai',
24 | 'karma-sinon',
25 | 'karma-chrome-launcher'
26 | ],
27 |
28 | browserify: {
29 | watch: true,
30 | debug: true,
31 | transform: ['browserify-istanbul']
32 | },
33 |
34 | coverageIstanbulReporter: {
35 | // Specify a common output directory
36 | dir: 'tests/reports/coverage',
37 | reporters: [
38 | {type: 'lcov', subdir: 'report-lcov'},
39 | {type : 'html', subdir : './'}
40 | ],
41 | check: {
42 | each: {
43 | statements: 90,
44 | branches: 90,
45 | functions: 90,
46 | lines: 90
47 | }
48 | }
49 | },
50 |
51 | // Add preprocessor to the files that should be processed
52 | preprocessors: {
53 | 'tests/**/*spec.js': ['browserify']
54 | },
55 |
56 | // List of files / patterns to load in the browser
57 | files: [
58 | {pattern: 'karma.bootstrap.js', watched: true, included: true, served: true},
59 | {pattern: 'tests/styles/themes/light/light.css', watched: true, included: true, served: true},
60 | {pattern: 'tests/styles/themes/dark/dark.css', watched: true, included: true, served: true},
61 | {pattern: 'tests/**/*spec.js', watched: true, included: true, served: true}
62 | ],
63 |
64 | client: {
65 | mocha: {
66 | reporter: 'html', // Change Karma's debug.html to the mocha web reporter
67 | ui: 'bdd'
68 | }
69 | },
70 |
71 | // List of files to exclude
72 | exclude: [],
73 |
74 | // Test results reporter to use
75 | // possible values: 'dots', 'progress'
76 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
77 | reporters: ['progress', 'coverage-istanbul'],
78 |
79 | // Web server port
80 | port: 9876,
81 |
82 | // Enable / disable colors in the output (reporters and logs)
83 | colors: true,
84 |
85 | // Level of logging
86 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
87 | logLevel: config.LOG_DEBUG,
88 |
89 | // Enable / disable watching file and executing tests whenever any file changes
90 | autoWatch: true,
91 |
92 | // Start these browsers
93 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
94 | browsers: ['ChromeHeadless'],
95 |
96 | customLaunchers: {
97 | Chrome_travis_ci: {
98 | base: 'ChromeHeadless',
99 | flags: ['--no-sandbox']
100 | }
101 | },
102 |
103 | // Continuous Integration mode
104 | // if true, Karma captures browsers, runs the tests and exits
105 | singleRun: false,
106 |
107 | concurrency: 2
108 | };
109 |
110 | if (process.env.TRAVIS) {
111 | configuration.browsers = ['Chrome_travis_ci'];
112 | }
113 |
114 | config.set(configuration);
115 | };
116 |
--------------------------------------------------------------------------------
/app/scripts/content/main.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 | var utils = require('../modules/utils/utils.js');
4 | var highLighter = require('../modules/content/highLighter.js');
5 | var port = utils.getPort();
6 |
7 | function confirmScriptInjectionDone() {
8 | // Add this action to the Q
9 | // This is needed when the devtools are undocked from the current inspected window
10 | setTimeout(function () {
11 | port.postMessage({
12 | action: 'on-main-script-injection'
13 | });
14 | }, 0);
15 | }
16 |
17 | var DONE_FLAG = 'MAIN_SCRIPT_INJECTION_DONE';
18 | if (window[DONE_FLAG] === true) {
19 | confirmScriptInjectionDone();
20 | return;
21 | }
22 |
23 | // Inject needed scripts into the inspected page
24 | // ================================================================================
25 |
26 | /**
27 | * Inject javascript file into the page.
28 | * @param {string} source - file path
29 | * @callback
30 | */
31 | var injectScript = function (source, callback) {
32 | var script = document.createElement('script');
33 | script.src = chrome.runtime.getURL(source);
34 | document.head.appendChild(script);
35 |
36 | /**
37 | * Delete the injected file, when it is loaded.
38 | */
39 | script.onload = function () {
40 | script.parentNode.removeChild(script);
41 |
42 | if (callback) {
43 | callback();
44 | }
45 | };
46 | };
47 |
48 | injectScript('vendor/ToolsAPI.js', function () {
49 | injectScript('scripts/injected/main.js', function () {
50 | window[DONE_FLAG] = true;
51 | confirmScriptInjectionDone();
52 | });
53 | });
54 |
55 | // ================================================================================
56 | // Communication
57 | // ================================================================================
58 |
59 | /**
60 | * Send message to injected script.
61 | * @param {Object} message
62 | */
63 | var sendCustomMessageToInjectedScript = function (message) {
64 | document.dispatchEvent(new CustomEvent('ui5-communication-with-injected-script', {
65 | detail: message
66 | }));
67 | };
68 |
69 | // Name space for message handler functions.
70 | var messageHandler = {
71 |
72 | /**
73 | * Changes the highlighter position and size,
74 | * when an element from the ControlTree is hovered.
75 | * @param {Object} message
76 | */
77 | 'on-control-tree-hover': function (message) {
78 | highLighter.setDimensions(message.target);
79 | },
80 |
81 | 'on-hide-highlight': function () {
82 | highLighter.hide();
83 | },
84 |
85 | 'do-ping': function (message, messageSender, sendResponse) {
86 | // respond to ping
87 | sendResponse(true /* alive status */);
88 | }
89 | };
90 |
91 | // Listen for messages from the background page
92 | port.onMessage(function (message, messageSender, sendResponse) {
93 | // Resolve incoming messages
94 | utils.resolveMessage({
95 | message: message,
96 | messageSender: messageSender,
97 | sendResponse: sendResponse,
98 | actions: messageHandler
99 | });
100 |
101 | // Send events to injected script
102 | sendCustomMessageToInjectedScript(message);
103 | });
104 |
105 | /**
106 | * Listener for messages from the injected script.
107 | */
108 | document.addEventListener('ui5-communication-with-content-script', function sendEvent(detectEvent) {
109 | // Send the received event detail object to background page
110 | port.postMessage(detectEvent.detail);
111 | }, false);
112 | }());
113 |
--------------------------------------------------------------------------------
/app/scripts/modules/injected/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Creates a parser that simplifies complex objects by removing non-serializable functions and complex instances.
5 | * @constructor
6 | */
7 | function ObjectParser() {
8 | }
9 |
10 | /**
11 | * Checks whether a given object is a simple/plain object.
12 | * @param {Object} object - input object, must not be null
13 | * @returns {boolean} true if simple object, false else
14 | * @private
15 | */
16 | ObjectParser.prototype._isSimpleObject = function (object) {
17 | // Check if toString output indicates object
18 | if (typeof object.toString === 'function' && object.toString() !== '[object Object]') {
19 | return false;
20 | }
21 | var proto = object.prototype;
22 | // Check if prototype is missing
23 | if (!proto) {
24 | return true;
25 | }
26 | // Check if constructed by a global object function
27 | var Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
28 | return typeof Ctor === 'function' && Ctor.toString() === 'function() {}';
29 | };
30 |
31 | /**
32 | * Deep copies an object.
33 | * @param {Object} object - the object, must not be null
34 | * @param {Array} predecessors - list of predecessors to detect circular references
35 | * @returns {Array|Object} the deep copied object
36 | * @private
37 | */
38 | ObjectParser.prototype._deepCopy = function (object, predecessors) {
39 | this.visitedObjects.push(object);
40 | var targetObject = Array.isArray(object) ? [] : {};
41 | this.createdObjects.push(targetObject);
42 | var currentPredecessors = predecessors.slice(0);
43 | currentPredecessors.push(object);
44 | for (var sKey in object) {
45 | // Ignore undefined and functions (similar to JSON.stringify)
46 | if (object[sKey] !== undefined && typeof object[sKey] !== 'function') {
47 | // Recursive call
48 | targetObject[sKey] = this._parseObject(object[sKey], currentPredecessors);
49 | }
50 | }
51 | return targetObject;
52 | };
53 |
54 | /**
55 | * Parses an object recursively.
56 | * @param {*} object - the object to parse, can be a simple type
57 | * @param {Array} predecessors - list of predecessors to detect circular references
58 | * @returns {*} returns the parsed object
59 | * @private
60 | */
61 | ObjectParser.prototype._parseObject = function (object, predecessors) {
62 | // Resolve simple type
63 | if (object === null || typeof object === 'number' || typeof object === 'boolean' || typeof object === 'string') {
64 | return object;
65 | }
66 | // Ignore complex types
67 | if (!Array.isArray(object) && !this._isSimpleObject(object)) {
68 | return '