Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🚀 Feature]: Language bindings for Firefox Prefs JS API #14266

Closed
MatzFan opened this issue Jul 16, 2024 · 7 comments
Closed

[🚀 Feature]: Language bindings for Firefox Prefs JS API #14266

MatzFan opened this issue Jul 16, 2024 · 7 comments

Comments

@MatzFan
Copy link
Contributor

MatzFan commented Jul 16, 2024

Feature and motivation

I would like to be able to get the value of a preference set in about:config dynamically. These are set when instantiating a driver via Options[:prefs] values). I'd also like to be able to dynamically set a preference.

The JS API for Firefox prefs seems straightforward. I do note that Firefox CDP is being deprecated IFO BiDi, could someone confirm whether this JS API is part of that scope or not?

Usage example

From your own Ruby tests I see here that you get a preference value like so, by passing a JS string to the Driver#execute_script method.

dir  = driver.execute_script("return Services.prefs.getStringPref('browser.download.dir')")

Something like this would be much better:

driver.pref['browser.download.dir']
# => ""

And the equivalent to set a pref value:

driver.pref['browser.download.dir'] = 'foo/bar'
driver.pref['browser.download.dir']
# => "/foo/bar"
Copy link

@MatzFan, thank you for creating this issue. We will troubleshoot it as soon as we can.


Info for maintainers

Triage this issue by using labels.

If information is missing, add a helpful comment and then I-issue-template label.

If the issue is a question, add the I-question label.

If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

After troubleshooting the issue, please add the R-awaiting answer label.

Thank you!

@MatzFan MatzFan changed the title [🚀 Feature]: Language bindings for Firefox JS API [🚀 Feature]: Language bindings for Firefox Prefs JS API Jul 29, 2024
@MatzFan
Copy link
Contributor Author

MatzFan commented Jul 29, 2024

So as a proof of concept I've created a Ruby module to do this. It can get and set prefs exactly as set out above. I've omitted the tests here for brevity, please let me know if you are interested in supporting this - or if I am reinventing a wheel!

module Selenium
  module WebDriver
    module Firefox
      # representation of a Firefox pref API: https://firefox-source-docs.mozilla.org/devtools/preferences.html
      class Preference
        PREF_TYPES = { 'PREF_BOOL' => 128, 'PREF_INT' => 64, 'PREF_INVALID' => 0, 'PREF_STRING' => 32 }.freeze
        GET_METHODS = { 0 => 'getStringPref', 32 => 'getStringPref', 64 => 'getIntPref', 128 => 'getBoolPref' }.freeze
        SET_METHODS = { 32 => 'setStringPref', 64 => 'setIntPref', 128 => 'setBoolPref' }.freeze

        def initialize(driver)
          @driver = driver
        end

        def [](str)
          execute_script_and_preserve_context str
        end

        def []=(str, val)
          execute_script_and_preserve_context str, val
        end

        private

        def execute_script_and_preserve_context(str, val = nil)
          @driver.in_chrome_context { @driver.execute_script(script_string(str, val)) }
        end

        def script_string(str, val)
          val ? setter_script(str, val) : getter_script(str)
        end

        def getter_script(str)
          type = pref_type(str)
          "return Services.prefs.#{GET_METHODS[type]}('#{str}'#{default(type)})"
        end

        def default(type)
          type.zero? ? ", ''" : '' # empty string default arg if pref not found, otherwise no default arg
        end

        def setter_script(str, val)
          "Services.prefs.#{set_method(str, val)}('#{str}', #{quote_str(val)})"
        end

        def set_method(str, val)
          type = pref_type(str)
          return SET_METHODS[type] unless type.zero?
          return 'setBoolPref' if val.instance_of?(TrueClass) || val.instance_of?(FalseClass)
          return 'setIntPref' if val.is_a? Integer

          'setStringPref' # everthing else gets set as a string
        end

        def pref_type(string)
          @driver.execute_script("return Services.prefs.getPrefType('#{string}')")
        end

        def quote_str(value)
          value.is_a?(String) ? "'#{value}'" : value
        end
      end

      # adds Driver#pref method and [] and []= methods on that object
      module FirefoxPrefs
        def pref
          in_chrome_context { Preference.new(self) }
        end

        def in_chrome_context
          old_context = context
          self.context = 'chrome'
          yield
        ensure
          self.context = old_context
        end
      end

      class Driver
        prepend FirefoxPrefs
      end
    end
  end
end

@MatzFan
Copy link
Contributor Author

MatzFan commented Jul 31, 2024

FWIW here is an example of a Python project using Selenium which uses the Firefox Prefs JS API to set driver prefs.

@diemol
Copy link
Member

diemol commented Jul 31, 2024

We are looking for projects to host on https://github.com/seleniumhq-community/, if you wish to work on this, it would be a great plugin that can be added as a dependency by any user. Would you be interested in doing that?

@MatzFan
Copy link
Contributor Author

MatzFan commented Aug 1, 2024

@diemol if you are not interested in incorporating such functionality directly into Selenium (it adds a dependency on the Firefox Prefs JS API) I was planning to simply release my code as a Ruby gem. Are Selenium Community repos simply mirrors of contributors' projects? If so I see no reason why I wouldn't set up a mirror there - and also for my Selenium Tor Browser project too. Can you point me to the docs on Selenium Community?

@diemol
Copy link
Member

diemol commented Aug 1, 2024

Selenium is meant to be a base library where users can build things on top. This use case is the perfect example of that.

The community org is meant to host projects driven and owned by the community, with the advantage of using the Selenium name to promote them. We don't have any formal docs because we are getting started. If you are interested, please join our Slack workspace, and we can chat about this.

@diemol
Copy link
Member

diemol commented Nov 5, 2024

I will close this as the issue has not had any more activity.

@diemol diemol closed this as not planned Won't fix, can't repro, duplicate, stale Nov 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants